Getting Started
Authentication
All API requests require a Bearer token in the Authorization header:
Authorization: Bearer <YOUR_API_KEY>
API keys are scoped to an organization. You receive one when completing the onboarding flow (see the Onboarding Links guide).
Error responses for invalid or missing authentication:
401 Unauthorized— Missing or invalid API key403 Forbidden— API key doesn't have access to the requested resource
Core Concepts
Data Model
Organization
├── API Keys
├── Onboarding Links
├── Shared Inbox Links
├── Outgoing Webhooks
└── WABAs (WhatsApp Business Accounts)
└── Phone Numbers
└── Conversations
└── Messages
- Organization — Your top-level account. All resources belong to an organization.
- WABA — A WhatsApp Business Account from Meta. Each WABA has its own access token and can have multiple phone numbers.
- Phone Number — A WhatsApp phone number connected to a WABA. Messages are sent and received through phone numbers.
- Contact — A WhatsApp customer known inside one connected phone inbox. It can have a phone number, WhatsApp ID, BSUID, and username.
- Conversation — A thread between a phone number and a contact identity. Do not assume every contact has a phone number.
- Message — A single message within a conversation (inbound or outbound).
- Shared Inbox Link — A public URL bound to one phone number's inbox with read/reply permissions.
IDs
The API uses two kinds of IDs:
| ID Type | Format | Example |
|---|---|---|
| Internal IDs | UUID v4 | 550e8400-e29b-41d4-a716-446655440000 |
| Meta IDs | Numeric string | 123456789012345 |
Most endpoints use internal UUIDs. Meta IDs appear in webhook payloads and when interfacing with Meta's Graph API.
When handling message webhooks, store data.contactIdentifier as your contact key and send replies
to data.contactReplyTo. data.contactPhone is legacy display data and can be null. See
Migrating from Phone to Contact Identity.
Quick Start
1. Connect a WhatsApp Number
Create an onboarding link and send it to your end-user:
curl -X POST https://api.example.com/v1/onboarding-links \
-H "Authorization: Bearer sk_org_..." \
-H "Content-Type: application/json" \
-d '{"name": "Acme Corp", "redirectUrl": "https://myapp.com/callback"}'
Send the hostedUrl from the response to your end-user. They complete Meta's Embedded Signup in the browser. See the Onboarding Links guide for the full flow.
2. Check Phone Health
Before sending production traffic, fetch the phone health snapshot:
curl https://api.example.com/v1/phones/{phoneId}/health \
-H "Authorization: Bearer sk_org_..."
Look at data.capabilities.canReceiveMessages:
truemeans the platform currently considers the phone ready to receive inbound messagesfalsemeans the phone is not fully ready yet, even if it is already linked to a WABA
This endpoint also triggers an asynchronous resync against Meta in the background. If the local state was stale, a subsequent call will reflect the refreshed values.
3. Send a Message
Before sending outbound traffic, confirm data.capabilities.canSendMessages is true:
curl -X POST https://api.example.com/v1/phones/{phoneId}/messages \
-H "Authorization: Bearer sk_org_..." \
-H "Content-Type: application/json" \
-d '{"type": "text", "to": "5491155551234", "text": "Hello!"}'
See the Sending Messages guide for all message types.
4. Optionally Expose a Shared Inbox
If you want an external user to work inside the inbox UI without giving them your org API key, create a shared inbox link:
curl -X POST https://api.example.com/v1/shared-inbox-links \
-H "Authorization: Bearer sk_org_..." \
-H "Content-Type: application/json" \
-d '{
"phoneNumberId": "660e8400-e29b-41d4-a716-446655440001",
"name": "Support access",
"permissions": {
"readInbox": true,
"readDetails": true,
"markRead": true,
"sendText": true,
"sendMedia": true,
"sendTemplates": true,
"sendAdvanced": false
}
}'
Send the publicUrl from the response to the external user. See the Shared Inbox Links guide for permissions, expiry, rotation, and revoke flows.
5. Receive Messages via Webhooks
Register a webhook to get notified of incoming messages and status updates:
curl -X POST https://api.example.com/v1/outgoing-webhooks \
-H "Authorization: Bearer sk_org_..." \
-H "Content-Type: application/json" \
-d '{
"url": "https://yourapp.com/webhooks/whatsapp",
"events": ["message.received", "message.sent"],
"secret": "whsec_your_secret_key"
}'
See the Webhooks guide for payload formats and signature verification.
Phone Readiness
The platform stores two related but different fields for each phone number:
| Field | Meaning |
|---|---|
isRegistered |
The phone has been registered with WhatsApp / Cloud API |
capabilities.canReceiveMessages |
The phone is ready for inbound traffic with the current app subscription and no blocking health issues |
capabilities.canSendMessages |
The phone is healthy enough for outbound messaging |
capabilities.canSendTemplates |
The phone is healthy enough for outbound template messaging |
Use the capability that matches your integration. For inbound-only flows, canReceiveMessages is usually the right operational flag.
Response Format
All responses follow a consistent envelope:
Success (single resource):
{
"data": { ... }
}
Success (list):
{
"data": [ ... ],
"meta": {
"pagination": {
"page": 1,
"limit": 50,
"total": 120,
"hasMore": true
}
}
}
Error:
{
"error": {
"message": "Resource not found",
"code": "NOT_FOUND"
}
}
Error Codes
| Code | HTTP Status | Description |
|---|---|---|
VALIDATION_ERROR |
400 | Invalid request body or parameters |
UNAUTHORIZED |
401 | Missing or invalid API key |
FORBIDDEN |
403 | API key doesn't have access |
NOT_FOUND |
404 | Resource not found |
INTERNAL_ERROR |
500 | Unexpected server error |