Migrate from Twilio
Step-by-step guide to migrate WhatsApp, SMS, and voice from Twilio to Zernio API
This guide walks you through migrating a Twilio messaging integration to Zernio. It covers the surfaces that map directly: WhatsApp Business API, SMS/MMS, voice calling, and phone numbers (including porting your numbers over).
Zernio is not a drop-in for all of Twilio. If your integration is mostly messaging, the move is mechanical and this page covers every call you'll change. If you run complex TwiML voice apps, Verify, or SendGrid, keep those on Twilio; see What doesn't map below.
Quick Reference
| What | Twilio | Zernio |
|---|---|---|
| Auth | HTTP Basic (Account SID + token, or API key) | Authorization: Bearer KEY |
| Wire format | application/x-www-form-urlencoded | JSON |
| WhatsApp send | POST /2010-04-01/.../Messages.json with From=whatsapp:+E164 | POST /v1/inbox/conversations (+ /{id}/messages) |
| SMS send | Same Messages.json endpoint | POST /v1/sms/messages |
| WhatsApp templates | Content API (ContentSid, HX...) | /v1/whatsapp/templates, addressed by name |
| Inbound webhooks | Form-encoded POST, reply with TwiML | JSON events, reply with any 2xx |
| Webhook signature | X-Twilio-Signature (HMAC-SHA1 of URL + sorted params) | X-Zernio-Signature (HMAC-SHA256 of raw body) |
| Phone numbers | IncomingPhoneNumbers + Regulatory Bundles | /v1/phone-numbers purchase + KYC, or port your numbers in |
| Multi-tenant unit | Subaccounts | Profiles |
Before You Start
What you'll need:
- Your Twilio Account SID and auth token (to export data and wind down)
- A Zernio account (sign up here) and an API key from your dashboard
How the concepts map
| Twilio | Zernio | Notes |
|---|---|---|
| Account / Subaccount | Team / Profile | Profiles group numbers and accounts per customer or brand |
| WhatsApp Sender | WhatsApp account (accountId) | From GET /v1/accounts?platform=whatsapp |
Phone number (PN...) | Phone number (/v1/phone-numbers) | One number can carry WhatsApp, SMS, and Voice together |
Message resource (SM...) | Message inside an inbox conversation | Zernio is conversation-centric |
| Conversations API conversation | Inbox conversation | GET /v1/inbox/conversations |
Content template (HX...) | WhatsApp template (by name) | Approved templates live on your WABA at Meta |
| Messaging Service | No equivalent | No sender pools; you send from a specific number |
| Studio flow | Workflow (/v1/workflows) | Visual builder with AI nodes, conditions, handoff |
The biggest shift is the send model. Twilio is stateless: every send is To + From on one endpoint. Zernio is conversation-centric for WhatsApp: business-initiated template sends create or reuse a conversation (POST /v1/inbox/conversations), and free-form replies go into it (POST /v1/inbox/conversations/{conversationId}/messages). SMS keeps the familiar shape: POST /v1/sms/messages with from and to, and replies thread into the same inbox.
Step 1: Set Up Zernio Profiles
For each Twilio subaccount (or just once for a single-tenant app), create a profile:
curl -X POST https://zernio.com/api/v1/profiles \
-H "Authorization: Bearer YOUR_ZERNIO_API_KEY" \
-H "Content-Type: application/json" \
-d '{"name": "Acme Corporation"}'Save the returned profile._id; it's the profileId for everything below.
Step 2: Move Your Numbers
Zernio has a full port-in API, so the numbers your customers already know can move with you:
Check portability (free)
curl -X POST https://zernio.com/api/v1/phone-numbers/port-in/check \
-H "Authorization: Bearer YOUR_ZERNIO_API_KEY" \
-d '{"phoneNumbers": ["+13025551234"]}'Upload the paperwork
Upload the Letter of Authorization and a recent Twilio invoice via POST /v1/phone-numbers/port-in/documents (one call per document; each returns a documentId).
Submit the port
curl -X POST https://zernio.com/api/v1/phone-numbers/port-in \
-H "Authorization: Bearer YOUR_ZERNIO_API_KEY" \
-d '{
"phoneNumbers": ["+13025551234"],
"endUser": {
"entityName": "Acme Corporation",
"authPersonName": "Jane Smith",
"streetAddress": "123 Market St",
"locality": "San Francisco",
"administrativeArea": "CA",
"postalCode": "94103",
"countryCode": "US"
},
"loaDocumentId": "DOC_ID_1",
"invoiceDocumentId": "DOC_ID_2"
}'Track status via GET /v1/phone-numbers/port-in (draft, pending, foc_confirmed, ported, exception, cancelled). Ported numbers arrive voice-ready, and SMS-ready where supported.
curl -X POST https://zernio.com/api/v1/phone-numbers/purchase \
-H "Authorization: Bearer YOUR_ZERNIO_API_KEY" \
-d '{"profileId": "PROFILE_ID", "country": "US", "wantsSms": true}'connectWhatsappdefaults totrue; passfalsefor a standalone Calls/SMS number (activates immediately, WhatsApp connectable later).wantsSms: trueprovisions from SMS-capable inventory (SMS capability is per number, not per country).- Your first purchase returns a Stripe
checkoutUrl; subsequent purchases provision inline. - Browse coverage and flat monthly prices with
GET /v1/phone-numbers/countries; search specific inventory (area code, city, digit pattern) withGET /v1/phone-numbers/available.
Where Twilio uses Regulatory Bundles, Zernio uses per-country KYC: regulated countries return 202 {"status": "kyc_required", "kycUrl": ...} and you complete it via API or send your customer a white-label hosted KYC link (POST /v1/phone-numbers/kyc/share, with your branding).
Your WhatsApp sender's real assets (the WABA, approved templates, display name, quality rating, messaging tier) live at Meta, not at Twilio. Reconnecting the same number through Zernio brings them along:
curl -X GET "https://zernio.com/api/v1/connect/whatsapp?profileId=PROFILE_ID&redirect_url=https://yourapp.com/callback" \
-H "Authorization: Bearer YOUR_ZERNIO_API_KEY"Open the returned authUrl and complete Meta's embedded signup with the Facebook login that owns the WABA (the same one used for Twilio's "Continue with Facebook" flow). Multi-number WABAs get a phone selection step.
If you hold a Meta System User token, there's also a fully headless path: POST /v1/connect/whatsapp/credentials with {profileId, accessToken, wabaId, phoneNumberId}.
Message history does not transfer; export what you need from Twilio (GET /2010-04-01/.../Messages.json) before winding down. Approved templates survive the move because they belong to the WABA, but you'll address them by name instead of ContentSid.
Just testing? POST /v1/whatsapp/sandbox/sessions with {"phone": "+15551234567"} replaces Twilio's join <code> sandbox: your test phone gets a verification message and replies to activate. See Sandbox.
Step 3: Get Your Account IDs
Twilio addresses everything by phone number strings. Zernio addresses WhatsApp by accountId:
curl -X GET "https://zernio.com/api/v1/accounts?platform=whatsapp" \
-H "Authorization: Bearer YOUR_ZERNIO_API_KEY"Store the mapping from each whatsapp:+E164 sender to its Zernio account _id. SMS keeps using raw E.164 numbers in from/to. To turn SMS on for a number you purchased or ported, call POST /v1/phone-numbers/{id}/sms once (idempotent; US numbers also need registration, below).
Step 4: Update Message Sending
Business-initiated sends (outside the 24h window) swap ContentSid for the template name:
Twilio:
curl -X POST https://api.twilio.com/2010-04-01/Accounts/ACxxxx/Messages.json \
-u ACxxxx:auth_token \
-d "To=whatsapp:+15551234567" \
-d "From=whatsapp:+15557654321" \
-d "ContentSid=HXxxxxxxxxxxxx" \
-d 'ContentVariables={"1": "John", "2": "ORD-123"}'Zernio:
curl -X POST https://zernio.com/api/v1/inbox/conversations \
-H "Authorization: Bearer YOUR_ZERNIO_API_KEY" \
-d '{
"accountId": "abc123",
"participantId": "15551234567",
"templateName": "order_update",
"templateLanguage": "en_US",
"templateParams": ["John", "ORD-123"]
}'The response includes the conversationId; store it for replies. Sending again to the same participant reuses the same conversation.
Free-form messages inside the 24-hour window go into the conversation:
Twilio:
curl -X POST https://api.twilio.com/2010-04-01/Accounts/ACxxxx/Messages.json \
-u ACxxxx:auth_token \
-d "To=whatsapp:+15551234567" \
-d "From=whatsapp:+15557654321" \
-d "Body=Your order shipped!" \
-d "MediaUrl=https://example.com/receipt.png"Zernio:
curl -X POST https://zernio.com/api/v1/inbox/conversations/CONVERSATION_ID/messages \
-H "Authorization: Bearer YOUR_ZERNIO_API_KEY" \
-d '{
"accountId": "abc123",
"message": "Your order shipped!",
"attachmentUrl": "https://example.com/receipt.png",
"attachmentType": "image"
}'You get the conversationId from the create call, from every webhook, or from GET /v1/inbox/conversations. Voice notes (voiceNote: true), locations, contact cards, reactions, quote-replies (replyTo), read receipts, and typing indicators are all first-class; see Inbox.
Twilio:
curl -X POST https://api.twilio.com/2010-04-01/Accounts/ACxxxx/Messages.json \
-u ACxxxx:auth_token \
-d "To=+14155559876" \
-d "From=+13025551234" \
-d "Body=Your order shipped!" \
-d "MediaUrl=https://example.com/receipt.png"Zernio:
curl -X POST https://zernio.com/api/v1/sms/messages \
-H "Authorization: Bearer YOUR_ZERNIO_API_KEY" \
-H "Idempotency-Key: 4f9d2c9e-8a1b-4f30-9d2f-1c2b3a4d5e6f" \
-d '{
"from": "+13025551234",
"to": "+14155559876",
"text": "Your order shipped!",
"mediaUrls": ["https://example.com/receipt.png"]
}'Returns {id, conversationId, status: "sent"}. Notes:
- Max 10 segments per message (1530 GSM-7 or 670 unicode characters), validated up front.
mediaUrls(up to 10) makes it an MMS.- The
Idempotency-Keyheader gives you safe retries (same key + same body replays the original response). - Replies thread into the same inbox conversation, and STOP/START handling is automatic (audit trail via
GET /v1/sms/opt-outs). - Carrier/line-type lookup:
GET /v1/sms/lookup?number=+1....
No SMS scheduling. Twilio's ScheduleType=fixed + SendAt has no Zernio SMS equivalent today; schedule sends from your own job queue. For WhatsApp, scheduled bulk sends exist via Broadcasts (POST /v1/broadcasts/{id}/schedule).
Twilio expresses rich WhatsApp messages as Content API types; Zernio expresses them inline on the send:
| Twilio content type | Zernio equivalent |
|---|---|
twilio/text | message |
twilio/media | attachmentUrl + attachmentType |
twilio/quick-reply | buttons: [{type: "postback", title, payload}] |
twilio/call-to-action (URL) | interactive: {type: "cta_url", ...} |
twilio/list-picker | interactive: {type: "list", ...} (session-only on both platforms) |
twilio/card, twilio/carousel | interactive: {type: "carousel", ...} (2-10 media or product cards) |
twilio/catalog | interactive: {type: "catalog_message"}, plus product / product_list |
twilio/location | location: {latitude, longitude, name, address} |
whatsapp/authentication | AUTHENTICATION template with an otp button |
whatsapp/flows | Flows API + POST /v1/whatsapp/flows/send |
Example, quick replies:
{
"accountId": "abc123",
"message": "Confirm your appointment?",
"buttons": [
{"type": "postback", "title": "Yes", "payload": "confirm_yes"},
{"type": "postback", "title": "No", "payload": "confirm_no"}
]
}Button taps arrive on the message.received webhook with metadata.interactiveType and metadata.interactiveId (Twilio's ButtonText / ButtonPayload). Full payload shapes: Commerce Messages and Inbox.
Step 5: Update Webhooks
This is the biggest paradigm change in the migration:
| Twilio | Zernio | |
|---|---|---|
| Payload | Form-encoded params (From, Body, WaId, ...) | JSON events with nested objects |
| Response | TwiML (<Response/>) | Any 2xx, body ignored |
| Registration | Per number / Messaging Service URL | POST /v1/webhooks/settings (team-wide, event-filtered) |
| Signature | X-Twilio-Signature: HMAC-SHA1 over URL + alphabetically sorted params, base64 | X-Zernio-Signature: HMAC-SHA256 over the raw JSON body, hex |
| Delivery status | StatusCallback per message | message.sent / message.delivered / message.read / message.failed events (WhatsApp) |
| Retries | 1 retry, only on connection timeout (by default) | Up to 7 attempts over ~51 hours, then dead-letter |
| Dedup | MessageSid | payload.id (also X-Zernio-Event-Id header) |
Register once:
curl -X POST https://zernio.com/api/v1/webhooks/settings \
-H "Authorization: Bearer YOUR_ZERNIO_API_KEY" \
-d '{
"name": "messaging-events",
"url": "https://yourapp.com/webhooks/zernio",
"events": [
"message.received", "message.sent", "message.delivered",
"message.read", "message.failed", "conversation.started",
"whatsapp.template.status_updated", "call.received", "call.ended"
],
"secret": "your-signing-secret"
}'Field mapping (inbound WhatsApp message)
| Twilio param | Zernio (message.received payload) |
|---|---|
MessageSid | message.platformMessageId (the wamid) |
From (whatsapp:+1...) / WaId | message.sender.id / message.sender.phoneNumber |
ProfileName | message.sender.name |
Body | message.text |
MediaUrl0..N | message.attachments[].url |
ButtonText / ButtonPayload | metadata.interactiveType + metadata.interactiveId |
Latitude / Longitude / Address / Label | metadata.location |
ReferralSourceId, ReferralBody, ... | metadata.referral (CTWA, incl. ctwa_clid) |
| Cart orders (not available) | metadata.order |
Signature verification
Twilio's URL-plus-sorted-params HMAC-SHA1 dance becomes a plain HMAC over the body:
// Twilio (before): twilio.validateRequest(authToken, signature, url, params)
// Zernio (after):
const expected = crypto
.createHmac('sha256', process.env.WEBHOOK_SECRET)
.update(rawBody)
.digest('hex');
const valid = crypto.timingSafeEqual(
Buffer.from(expected),
Buffer.from(req.headers['x-zernio-signature'])
);SMS is not event-driven yet. Delivery-status and inbound-message webhooks fire for WhatsApp (and the other inbox platforms), not for SMS. Inbound SMS and delivery states land in the inbox: read them via GET /v1/inbox/conversations/{conversationId}/messages (each message carries deliveryStatus and any deliveryError). The send call returns the conversationId to poll.
Debug with GET /v1/webhooks/logs (30-day retention) and POST /v1/webhooks/test.
Step 6: Templates
If you moved your WhatsApp sender (Step 2), your approved templates are already available; verify with GET /v1/whatsapp/templates?accountId=abc123. Creating new ones swaps the Content API for Meta's native shape:
Twilio (two calls: create, then submit for approval):
curl -X POST https://content.twilio.com/v1/Content \
-u ACxxxx:auth_token \
-H "Content-Type: application/json" \
-d '{
"friendly_name": "order_update",
"language": "en",
"variables": {"1": "John"},
"types": {"twilio/text": {"body": "Hi {{1}}, your order is confirmed."}}
}'
curl -X POST https://content.twilio.com/v1/Content/HXxxxx/ApprovalRequests/whatsapp \
-u ACxxxx:auth_token \
-H "Content-Type: application/json" \
-d '{"name": "order_update", "category": "UTILITY"}'Zernio (one call):
curl -X POST https://zernio.com/api/v1/whatsapp/templates \
-H "Authorization: Bearer YOUR_ZERNIO_API_KEY" \
-d '{
"accountId": "abc123",
"name": "order_update",
"language": "en_US",
"category": "UTILITY",
"components": [
{
"type": "body",
"text": "Hi {{1}}, your order is confirmed.",
"example": {"body_text": [["John"]]}
}
]
}'Approval outcomes arrive on the whatsapp.template.status_updated webhook instead of polling ApprovalRequests.
Skip Meta review entirely for common flows with pre-approved library templates: pass library_template_name on the create call and get APPROVED back immediately. Introspect a library template first via GET /v1/whatsapp/template-library?accountId=...&name=.... See Templates.
US A2P Registration
Brand and Campaign registrations made through Twilio belong to Twilio's CSP account at The Campaign Registry, so they don't transfer between providers; US long-code SMS needs a fresh registration with Zernio (same underlying registry, similar fields):
curl -X POST https://zernio.com/api/v1/sms/registrations \
-H "Authorization: Bearer YOUR_ZERNIO_API_KEY" \
-d '{
"registrationType": "standard_10dlc",
"phoneNumbers": ["+13025551234"],
"brand": {
"entityType": "PRIVATE_PROFIT",
"displayName": "Acme",
"companyName": "Acme Corporation",
"ein": "12-3456789",
"country": "US",
"vertical": "TECHNOLOGY",
"street": "123 Market St", "city": "San Francisco",
"state": "CA", "postalCode": "94103"
},
"campaign": {
"usecase": "CUSTOMER_CARE",
"description": "Order updates and customer support replies for Acme customers.",
"messageFlow": "Customers opt in at checkout by checking the SMS updates box.",
"sample1": "Your Acme order ORD-123 has shipped.",
"helpMessage": "Acme support: reply with your question or email help@acme.com.",
"optinKeywords": "START", "optinMessage": "You are subscribed to Acme updates. Reply STOP to opt out.",
"optoutKeywords": "STOP", "optoutMessage": "You have been unsubscribed from Acme updates.",
"helpKeywords": "HELP"
}
}'sole_prop_10dlc(no EIN) verifies via an OTP texted to your mobile (POST /v1/sms/registrations/{id}/verify-otp);toll_freecovers toll-free verification.- Poll
GET /v1/sms/registrations/{id}forpendingtoapproved; rejected campaigns can appeal (POST /{id}/appeal). - Onboarding clients?
POST /v1/sms/registrations/sharemints a hosted single-use link so your customer fills in their own legal details, no Zernio login needed. - Additional numbers reuse an approved campaign via
POST /v1/phone-numbers/{id}/sms/reuse-registration(no new registration).
Sequence the cutover: US numbers deliver SMS only once registration is approved. Start the Zernio registration while Twilio still carries your traffic, and switch sending after status: "approved".
Voice
Twilio's voice model is programmable (your server returns TwiML per call). Zernio's is configuration plus API calls, which covers the common business-phone patterns without an app server:
- Inbound:
POST /v1/phone-numbers/{id}/voiceconfigures forwarding (tel:,sip:, orwss://for AI voice agents), voicemail with a custom greeting, business hours, an IVR menu (up to 12 keys), recording, and transcription. All settings, no TwiML. - Outbound:
POST /v1/voice/callswithto, optional spokengreeting, answering-machine detection, andvoicemailDropMessage(speak a message to a machine and hang up). Blind transfer viaPOST /v1/voice/calls/{id}/transfer. Cost preview viaGET /v1/voice/calls/estimate. - Browser calling:
POST /v1/voice/calls/webissues a short-lived WebRTC token for a dashboard-style softphone. - History + recordings: unified across PSTN and WhatsApp calls at
GET /v1/calls(per-call cost breakdown; full transcripts onGET /v1/calls/{id}); recordings viaGET /v1/calls/{id}/recording. - WhatsApp Business Calling: supported on both platforms; on Zernio see Calling (call permissions, the
voice_callinteractive button, recording). - Call lifecycle webhooks:
call.received,call.ended(with duration and cost),call.failed.
TwiML apps don't port. If your Twilio voice logic is a dynamic TwiML application (custom IVR trees beyond a keypress menu, conferencing, SIP trunking, call queues), Zernio doesn't replace it. Keep voice on Twilio and migrate messaging, or simplify to the config model above.
Feature Map
| Feature | Twilio | Zernio |
|---|---|---|
| WhatsApp messaging | Messages API + Content API | Inbox API + Templates |
| WhatsApp rich/interactive | Content types | interactive field incl. commerce messages |
| WhatsApp flows | whatsapp/flows content type | Full Flows API (create, publish, send, responses) |
| Bulk + drip messaging | Your own loop / Studio | Broadcasts with scheduling, sequences |
| SMS / MMS | Messages API | POST /v1/sms/messages |
| US A2P compliance | Brand/Campaign APIs | /v1/sms/registrations (+ hosted share links) |
| Opt-out handling | Default + Advanced Opt-Out | Automatic STOP/START + GET /v1/sms/opt-outs |
| Number lookup | Lookup API | GET /v1/sms/lookup |
| Phone numbers | Buy + Regulatory Bundles | Buy + KYC (+ white-label KYC links), port-in API |
| Voice | Programmable (TwiML) | Config-based forwarding, IVR, voicemail + calls API |
| Conversation automation | Studio (separate product) | Workflows built in, with AI nodes |
| Team inbox UI | Not included (Flex is a separate paid contact-center product) | Included dashboard inbox |
| Sandbox | join <code> shared number | Verification-reply sandbox |
You also gain: group chats API, a unified inbox that covers Instagram, Facebook, Telegram, and the rest of the social platforms alongside WhatsApp and SMS, template library instant approvals, and hosted onboarding links for your end customers (connect, KYC with white-label branding, A2P registration).
No Zernio equivalent (keep these on Twilio or plan around them):
- Programmable voice at TwiML depth (dynamic call flows, conferencing, SIP trunking, queues).
- Verify (managed OTP over SMS/voice/email), Video, SendGrid email, RCS.
- SMS message scheduling (
SendAt); schedule from your own queue. - Messaging Services sender pools (sticky sender, geomatch, alphanumeric sender IDs, short codes).
- Link shortening with click tracking.
- Serverless Functions; your webhook handlers run on your own infrastructure.
- SMS webhooks: inbound SMS and delivery receipts are read via the inbox API, not pushed as events (WhatsApp has full event coverage).
Cutover Strategy
| Phase | Actions |
|---|---|
| Prep | Create profiles, build against the sandbox, start US A2P registration early |
| Reconnect senders through Zernio one number at a time (hard cutover per number: reconnect, verify webhooks, stop Twilio sends for it) | |
| SMS | Once registration is approved: port numbers in (or switch to new ones), flip POST /v1/sms/messages |
| Voice | Configure forwarding/IVR per number, or keep complex voice on Twilio |
| Cutoff | Export message history from Twilio, then release the Twilio numbers you ported away |
Meta's per-template conversation charges continue either way (they're billed for the WABA regardless of provider), and template approvals survive the move because they live on the WABA.
Troubleshooting
| Symptom | Twilio equivalent | Fix |
|---|---|---|
TEMPLATE_REQUIRED on create conversation | Error 63016 (outside window) | Business-initiated WhatsApp needs an approved template; free-form only inside the 24h window |
| 401 Unauthorized | Basic auth credentials | Use Authorization: Bearer YOUR_API_KEY |
| Form-encoded body rejected | Twilio wire format | Send JSON with Content-Type: application/json |
| SMS blocked for US number | Error 30034 (unregistered 10DLC) | Complete /v1/sms/registrations and wait for approved |
| Recipient gets nothing after STOP | Error 21610 | Recipient must text START; check GET /v1/sms/opt-outs |
| Sandbox can't message a phone | Error 63015 (recipient hasn't joined the sandbox) | Activate the phone via POST /v1/whatsapp/sandbox/sessions + verification reply |
| Template send ignores variables | ContentVariables JSON string | Use templateParams (array, in placeholder order) on create-conversation |
| Webhook signature mismatch | URL/params HMAC-SHA1 | Verify HMAC-SHA256 of the raw body against X-Zernio-Signature |
Code Example: Node.js
const ZERNIO = 'https://zernio.com/api/v1';
const headers = {
'Authorization': `Bearer ${process.env.ZERNIO_API_KEY}`,
'Content-Type': 'application/json',
};
// Twilio: client.messages.create({ to: 'whatsapp:+1...', contentSid, contentVariables })
const sendWhatsAppTemplate = async ({ accountId, to, templateName, templateParams = [] }) => {
const res = await fetch(`${ZERNIO}/inbox/conversations`, {
method: 'POST',
headers,
body: JSON.stringify({
accountId,
participantId: to, // digits with country code, no whatsapp: prefix
templateName, // template name instead of ContentSid
templateLanguage: 'en_US',
templateParams,
}),
});
const { data } = await res.json();
return data; // { messageId, conversationId, ... }
};
// Twilio: client.messages.create({ to, from, body })
const sendSms = async ({ from, to, text }) => {
const res = await fetch(`${ZERNIO}/sms/messages`, {
method: 'POST',
headers: { ...headers, 'Idempotency-Key': crypto.randomUUID() },
body: JSON.stringify({ from, to, text }),
});
return res.json(); // { id, conversationId, status: "sent" }
};Need Help?
- WhatsApp platform guides: /platforms/whatsapp
- Support: support@zernio.com
- Rate Limits: 60-1,200 req/min, bucketed by your team's connected-account count (0-2: 60, 3-2,000: 600, 2,001+: 1,200), see /guides/rate-limits