Documentation
One contract for
SMS, WhatsApp, and OTP.
REST, JSON, and bearer tokens. This page covers the parts you will use most. The full contract, response examples, and field list live in the app documentation.
01 / Authentication
A token carries permissions, not just an identity.
Send the token in the Authorization header. Permissions are checked per endpoint, so a token with only sms:read cannot create a batch. Create one token per integration so you can revoke it without touching the others.
Authorization: Bearer notif.<64 hex characters>Content-Type: application/jsonAccept: application/jsonIdempotency-Key: <unique per write operation># Create a token on the API Tokens page in the dashboard.# Every token has its own permission list:# sms:create, sms:read, rcs:create, rcs:read,# mms:create, mms:read,# whatsapp:create, whatsapp:read,# otp-sms:create, otp-sms:verify, otp-sms:read,# otp-whatsapp:create, otp-whatsapp:verify, otp-whatsapp:read# Never put a token in browser code or a client bundle.02 / OTP
Send, verify, track.
SMS and WhatsApp use the same request shape. Templates are capped at 255 characters and must contain {otp} or {code}. Codes stay valid for 60 to 900 seconds.
- POST
/api/v1/otp/sms/sendCreate an OTP request over SMS.
otp-sms:create - POST
/api/v1/otp/whatsapp/sendCreate an OTP request over WhatsApp.
otp-whatsapp:create - POST
/api/v1/otp/verifyVerify a code for a number and channel.
otp-sms:verify | otp-whatsapp:verify - GET
/api/v1/otp/{id}Check the status of one request.
otp-sms:read | otp-whatsapp:read
curl -s -X POST "$NOTIF_API_URL/api/v1/otp/sms/send" \ -H "Authorization: Bearer $NOTIF_TOKEN" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: login-2026-11-07-001" \ -d '{"recipient":"081234567890","ttl":300}'curl -s -X POST "$NOTIF_API_URL/api/v1/otp/verify" \ -H "Authorization: Bearer $NOTIF_TOKEN" \ -H "Content-Type: application/json" \ -d '{"recipient":"081234567890","code":"123456","channel":"sms"}'03 / SMS, RCS, MMS
One endpoint, three message types.
The Idempotency-Key header is required when creating a batch. Set type to SMS, RCS, or MMS. RCS allows media, MMS requires it. Each request takes 1 to 1,000 recipients, and they must be unique.
- POST
/api/v1/smsCreate an SMS, RCS, or MMS batch.
sms:create - GET
/api/v1/smsList batches, 20 per page.
sms:read - GET
/api/v1/sms/{id}Batch details and the messages inside it.
sms:read - GET
/api/v1/sms/batches/{id}List the messages in one batch.
sms:read
04 / WhatsApp
One recipient per request.
The WhatsApp endpoint creates a batch with a single recipient. For large lists, use a blast session in the dashboard. Media is sent as an HTTPS URL, not base64.
- POST
/api/v1/whatsappSend a single WhatsApp message.
whatsapp:create - GET
/api/v1/whatsappList WhatsApp batches.
whatsapp:read - GET
/api/v1/whatsapp/{id}Details for one batch.
whatsapp:read - GET
/api/v1/whatsapp/batches/{id}List the messages in a batch.
whatsapp:read
05 / Virtual Numbers
Order statuses you need to know.
Virtual numbers are managed from the notif. panel, not from the public SMS or WhatsApp endpoints. Order status and OTP codes appear in the dashboard, and the code can be forwarded to a WhatsApp device through the integration.
Scroll the table sideways to see the other columns.
| Item | Value | Notes |
|---|---|---|
| ACTIVE | Waiting for SMS | The number is ready. Cancellation follows the window the partner allows. |
| OTP_RECEIVED | Code received | The code is available. The order can be completed or re-requested if the flag allows it. |
| COMPLETED | Completed | The order closes once the code is used. |
| CANCELED | Canceled | The order stopped before a code arrived. |
| EXPIRED | Expired | The active window ended without a code. |
06 / Webhooks
Verify the signature before you trust the payload.
Batch webhooks send sms.batch.updated and whatsapp.batch.updated events to a public HTTPS URL on port 443. Every request carries X-Webhook-Id, X-Webhook-Timestamp, and X-Webhook-Signature in the format sha256=HMAC(timestamp.body, secret).
{ "id": "9a51d6de-7f0c-4c1b-9f8a-2f7b5a6d8c31", "event": "sms.batch.updated", "created_at": "2026-11-07T09:30:00+00:00", "data": { "id": "01JBCDEFGHJKMNPQRSTVWXYZ", "status": "running", "lifecycle_status": "running" }}import { createHmac, timingSafeEqual } from "node:crypto";const timestamp = request.headers["x-webhook-timestamp"];const signature = request.headers["x-webhook-signature"];const expected = "sha256=" + createHmac("sha256", process.env.NOTIF_WEBHOOK_SECRET) .update(`${timestamp}.${rawBody}`) .digest("hex");const valid = timingSafeEqual( Buffer.from(expected), Buffer.from(String(signature)),);Verify signatures against the raw body, not the parsed JSON. Reject requests with an old timestamp, and do not follow redirects.
07 / Limits
The numbers that define the API contract.
Scroll the table sideways to see the other columns.
| Item | Value | Notes |
|---|---|---|
| Request limit | 120 / minute | Per token and per account. A 429 response includes Retry-After. |
| Recipients per SMS batch | 1 to 1,000 | Must be unique within one request. |
| Message length | 5,000 characters | Applies to SMS, RCS, and WhatsApp. |
| Media | HTTPS URL | Up to 2,048 characters. The 25 MB file limit applies to RCS and MMS. |
| OTP validity | 60 to 900 seconds | Defaults to 300 seconds. |
| OTP attempts | 5 | After that, the request is marked failed. |
| OTP cooldown | 60 seconds | At most 5 requests per hour for the same number and channel. |
| Message page size | 1 to 1,000 | Defaults to 100 messages per page in batch details. |
08 / Error codes
Decide what to do from the error code, not the message.
Error messages can change. Error codes are a stable enum, and they are what you should branch on.
Scroll the table sideways to see the other columns.
| Item | Value | Notes |
|---|---|---|
| UNAUTHORIZED | 401 | The token is missing, invalid, revoked, or expired. |
| FORBIDDEN | 403 | The token lacks permission for this endpoint. |
| NOT_FOUND | 404 | The ID does not exist or belongs to another account. |
| IDEMPOTENCY_KEY_REUSED | 409 | The same key was used with a different body. |
| VALIDATION_ERROR | 422 | A required field is empty, malformed, or over a limit. |
| RATE_LIMIT_EXCEEDED | 429 | Wait for Retry-After before trying again. |
| SERVICE_UNAVAILABLE | 503 | The service is off or the WhatsApp device is unavailable. |
| OTP_DISABLED | 503 | The OTP service is currently disabled. |
| WHATSAPP_DISABLED | 503 | The WhatsApp service is currently disabled. |
The full list, including channel-specific codes, is in the app documentation.
Need another example?
The app documentation has response examples, per-endpoint fields, and an explanation of every enum value. Start there before writing a new integration.
Open the app documentationStart with the right token.
Create a token with narrow permissions, then send your first request from your server.