
Security News
Attackers Are Hunting High-Impact Node.js Maintainers in a Coordinated Social Engineering Campaign
Multiple high-impact npm maintainers confirm they have been targeted in the same social engineering campaign that compromised Axios.
A TypeScript/JavaScript client for the OTPiq SMS service. This package provides a clean and type-safe way to interact with the OTPiq API, supporting verification codes, custom messages, sender IDs, and message tracking.
npm install otpiq
# or
yarn add otpiq
# or
pnpm add otpiq
import { OTPiqClient } from "otpiq";
// Initialize the client
const client = new OTPiqClient({
apiKey: "your_api_key_here",
});
// Send verification SMS with auto-generated code
const response = await client.sendSMS({
phoneNumber: "9647701234567",
smsType: "verification",
});
console.log("Generated code:", response.verificationCode);
console.log("SMS ID:", response.smsId);
console.log("Cost:", response.cost, "IQD");
console.log("Remaining credit:", response.remainingCredit, "IQD");
// Send custom message with sender ID
const customResponse = await client.sendSMS({
phoneNumber: "9647701234567",
smsType: "custom",
customMessage: "Welcome to our service!",
senderId: "MyBrand",
});
When no verification code is provided, the client automatically generates one:
const response = await client.sendSMS({
phoneNumber: "9647701234567",
smsType: "verification",
digitCount: 6, // Optional: customize length (default: 6)
});
const generatedCode = response.verificationCode;
You can provide your own verification code:
const response = await client.sendSMS({
phoneNumber: "9647701234567",
smsType: "verification",
verificationCode: "123456",
});
Custom messages require a sender ID and will automatically use the SMS provider:
const response = await client.sendSMS({
phoneNumber: "9647701234567",
smsType: "custom",
customMessage: "Your appointment is confirmed for tomorrow at 2 PM",
senderId: "MyBrand",
});
Retrieve all your approved sender IDs:
const senderIds = await client.getSenderIds();
console.log("Available sender IDs:", senderIds.data);
Here's a complete Next.js API route implementation:
// pages/api/send-verification.ts
import { OTPiqClient } from "otpiq";
import type { NextApiRequest, NextApiResponse } from "next";
export default async function handler(
req: NextApiRequest,
res: NextApiResponse
) {
if (req.method !== "POST") {
return res.status(405).json({ message: "Method not allowed" });
}
const client = new OTPiqClient({
apiKey: process.env.OTPIQ_API_KEY!,
});
try {
const response = await client.sendSMS({
phoneNumber: req.body.phoneNumber,
smsType: "verification",
});
// Store the code securely (e.g., in your database or session)
await storeVerificationCode({
phoneNumber: req.body.phoneNumber,
code: response.verificationCode,
smsId: response.smsId,
});
res.status(200).json({
message: "Verification code sent",
smsId: response.smsId,
});
} catch (error) {
if (error instanceof Error) {
res.status(500).json({ error: error.message });
} else {
res.status(500).json({ error: "An unknown error occurred" });
}
}
}
const client = new OTPiqClient({
apiKey: "your_api_key_here",
});
// Verification SMS with auto-generated code
const response = await client.sendSMS({
phoneNumber: "9647701234567",
smsType: "verification",
digitCount: 6, // Optional
provider: "auto", // Optional: 'auto' | 'sms' | 'whatsapp' | 'telegram'
});
// Verification SMS with custom code
const response = await client.sendSMS({
phoneNumber: "9647701234567",
smsType: "verification",
verificationCode: "123456",
provider: "whatsapp", // Optional
});
// Custom message with sender ID (always uses SMS provider)
const response = await client.sendSMS({
phoneNumber: "9647701234567",
smsType: "custom",
customMessage: "Your message here",
senderId: "MyBrand",
});
// Send via WhatsApp
const response = await client.sendWhatsApp({
phoneNumber: "9647701234567",
smsType: "verification",
verificationCode: "123456", // Optional
});
// Send via Telegram
const response = await client.sendTelegram({
phoneNumber: "9647701234567",
smsType: "verification",
});
// Send custom message (simplified method)
const response = await client.sendCustomMessage({
phoneNumber: "9647701234567",
customMessage: "Your order is ready for pickup!",
senderId: "MyBrand",
});
// Get just the credit balance
const credits = await client.getCredits();
console.log(`Available credits: ${credits} IQD`);
// Alias for trackSMS
const status = await client.getSMSStatus("sms-1234567890");
const status = await client.trackSMS("sms-1234567890");
console.log("Delivery status:", status.status);
console.log("Cost:", status.cost);
const info = await client.getProjectInfo();
console.log("Project name:", info.projectName);
console.log("Available credits:", info.credit);
const senderIds = await client.getSenderIds();
console.log("Available sender IDs:", senderIds.data);
interface SendSMSOptions {
phoneNumber: string;
smsType: "verification" | "custom";
verificationCode?: string | number;
customMessage?: string;
senderId?: string;
provider?: "auto" | "sms" | "whatsapp" | "telegram";
digitCount?: number;
deliveryReport?: DeliveryReport;
}
interface DeliveryReport {
webhookUrl: string;
deliveryReportType?: "all" | "final";
webhookSecret?: string;
}
interface SMSResponse {
message: string;
smsId: string;
remainingCredit: number;
cost: number;
canCover: boolean;
paymentType: "prepaid" | "postpaid";
verificationCode?: string; // Only included for verification SMS
}
interface SMSTrackingResponse {
status: "pending" | "sent" | "delivered" | "failed" | "expired";
phoneNumber: string;
smsId: string;
cost: number;
}
interface SenderId {
_id: string;
senderId: string;
status: "accepted" | "pending" | "rejected";
pricePerSms: {
korekTelecom: number;
asiaCell: number;
zainIraq: number;
others: number;
};
}
Configure webhooks to receive real-time delivery status updates for your messages:
const response = await client.sendSMS({
phoneNumber: "9647701234567",
smsType: "verification",
deliveryReport: {
webhookUrl: "https://your-app.com/webhooks/sms-status",
deliveryReportType: "all", // or "final" for final status only
webhookSecret: "your_secret_123" // optional, for security
}
});
Your webhook endpoint will receive POST requests with the following payload:
interface WebhookPayload {
smsId: string;
deliveryReportType: "all" | "final";
isFinal: boolean;
channel: "sms" | "whatsapp" | "telegram";
status: "sent" | "delivered" | "failed";
senderId?: string; // Only for SMS with custom sender IDs
reason?: string; // Only when status is 'failed'
}
app.post('/webhooks/sms-status', (req, res) => {
const payload: WebhookPayload = req.body;
const webhookSecret = req.headers['x-otpiq-webhook-secret'];
// Verify webhook secret if configured
if (webhookSecret !== process.env.WEBHOOK_SECRET) {
return res.status(401).send('Unauthorized');
}
// Process the webhook
console.log(`SMS ${payload.smsId} status: ${payload.status}`);
if (payload.status === 'delivered') {
// Handle successful delivery
} else if (payload.status === 'failed') {
console.error(`Delivery failed: ${payload.reason}`);
// Handle failure
}
// Always respond quickly (within 10 seconds)
res.status(200).send('OK');
});
"all" - Receive all status updates (sent, delivered, failed)"final" - Receive only final status (delivered or failed)The package includes built-in error classes for common API errors:
import {
OTPiqError,
InsufficientCreditError,
RateLimitError,
SpendingThresholdError,
SenderIdError,
TrialModeError,
ValidationError,
NotFoundError,
UnauthorizedError
} from "otpiq";
try {
await client.sendSMS({
phoneNumber: "9647701234567",
smsType: "verification",
});
} catch (error) {
if (error instanceof InsufficientCreditError) {
console.log(
`Need more credits! Required: ${error.requiredCredit}, ` +
`Available: ${error.yourCredit}, Can cover: ${error.canCover}`
);
} else if (error instanceof RateLimitError) {
console.log(
`Rate limit exceeded. Try again in ${error.waitMinutes} minutes. ` +
`Limit: ${error.maxRequests} requests per ${error.timeWindowMinutes} minutes`
);
} else if (error instanceof SpendingThresholdError) {
console.log(
`Spending threshold exceeded! Current: ${error.currentSpending}, ` +
`Threshold: ${error.spendingThreshold}, Transaction cost: ${error.cost}`
);
} else if (error instanceof TrialModeError) {
console.log("Trial mode: Can only send to your own phone number");
} else if (error instanceof SenderIdError) {
console.log("Sender ID error:", error.message);
} else if (error instanceof ValidationError) {
console.log("Validation error:", error.message);
} else if (error instanceof NotFoundError) {
console.log("Not found:", error.message);
} else if (error instanceof UnauthorizedError) {
console.log("Unauthorized:", error.message);
} else if (error instanceof OTPiqError) {
console.log("API Error:", error.message);
}
}
Contributions are welcome! Please feel free to submit a Pull Request. For major changes, please open an issue first to discuss what you would like to change.
MIT
For support:
FAQs
TypeScript client for the OTPiq SMS verification service
We found that otpiq demonstrated a healthy version release cadence and project activity because the last version was released less than a year ago. It has 1 open source maintainer collaborating on the project.
Did you know?

Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.

Security News
Multiple high-impact npm maintainers confirm they have been targeted in the same social engineering campaign that compromised Axios.

Security News
Axios compromise traced to social engineering, showing how attacks on maintainers can bypass controls and expose the broader software supply chain.

Security News
Node.js has paused its bug bounty program after funding ended, removing payouts for vulnerability reports but keeping its security process unchanged.