Getting Started
Base URL
Production: https://wpp-cloud.com/v1
All code samples in these guides use https://api.example.com as a placeholder — substitute the
base URL above (or your own deployment's URL). Every v1 endpoint is rooted at /v1.
Prerequisites
To integrate against this API you need exactly three things:
| Requirement | What it is | How to get it |
|---|---|---|
| Base URL | The API root for all requests | Documented above: https://wpp-cloud.com/v1 |
| API key | Organization-scoped Bearer token (sk_org_...) |
Issued by the platform administrator — request it from them. Keys cannot be self-created through the public API. |
| phoneId | Internal UUID of the WhatsApp line you will send/receive through | Connect a phone via the onboarding flow, or look it up if already connected (see below) |
1. Get an API key
API keys are scoped to an organization and are issued by the platform administrator — creating organizations and keys is not part of the public API. Ask your administrator for:
- Your organization's API key (
sk_org_...) - Optionally, the
phoneIdof an already-connected line
Verify the key works before anything else:
curl https://wpp-cloud.com/v1/organizations/me \
-H "Authorization: Bearer sk_org_..."
A 200 with your organization's data.id and data.name confirms the key is valid. A 401
means the key is wrong or was revoked.
2. Get the phoneId
The phoneId is the internal UUID of a connected phone number — it goes in every messaging
path (/v1/phones/{phoneId}/...). There are two ways to obtain it:
A. The phone is already connected — list your WABAs; each one includes its phone numbers:
curl https://wpp-cloud.com/v1/wabas \
-H "Authorization: Bearer sk_org_..."
{
"data": [
{
"id": "770e8400-e29b-41d4-a716-446655440002",
"wabaId": "987654321098765",
"phoneNumbers": [
{
"id": "660e8400-e29b-41d4-a716-446655440001",
"phoneNumberId": "123456789012345",
"phoneNumber": "+54 9 11 5555-1234",
"isRegistered": true,
"qualityRating": "GREEN",
"messagingLimit": "1K"
}
]
}
]
}
⚠️ Naming trap: in this response, the phone's
idfield (UUID) is thephoneIdyou use in API paths. The field literally namedphoneNumberIdis Meta's numeric ID — do not use it in/v1/phones/{phoneId}/...URLs.
B. The phone is not connected yet — run the onboarding flow:
POST /v1/onboarding-linkswith aname(and optionallyredirectUrl,webhookUrl) — see the Onboarding Links guide- Send the returned
hostedUrlto whoever owns the WhatsApp number; they complete Meta's Embedded Signup in the browser - When they finish, fetch
GET /v1/onboarding-links/{id}— the response now includesphoneNumberId(the internal UUID, i.e. yourphoneId),phoneNumber, andwabaId. Alternatively, listen for thephone_number.connectedwebhook event, which carries the same IDs
3. Verify the line is ready
Before wiring production traffic, confirm the phone can actually send and receive:
curl https://wpp-cloud.com/v1/phones/{phoneId}/health \
-H "Authorization: Bearer sk_org_..."
Check data.capabilities.canSendMessages and data.capabilities.canReceiveMessages — both should
be true. Then send yourself a test message:
curl -X POST https://wpp-cloud.com/v1/phones/{phoneId}/messages \
-H "Authorization: Bearer sk_org_..." \
-H "Content-Type: application/json" \
-d '{"type": "text", "to": "5491155551234", "text": "Test from the API"}'
A 200 with a wamid means the full chain works.
Authentication
All API requests require a Bearer token in the Authorization header:
Authorization: Bearer <YOUR_API_KEY>
API keys are scoped to an organization and are issued by the platform administrator (see Prerequisites above) — request yours from them.
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.
6. Optionally Subscribe to Realtime Events
The same events delivered to webhooks are also pushed over a WebSocket, with identical payloads — ideal for live UIs (inboxes, dashboards) without polling:
websocat -H "Authorization: Bearer sk_org_..." wss://api.example.com/v1/realtime
Webhooks remain the guaranteed delivery channel (retries + logs); the WebSocket is the low-latency one. See the Realtime WebSocket guide for browser auth (ephemeral client secrets), filtering, keepalive, and reconnection.
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 |
MESSAGE_SEND_FAILED |
400 | Meta rejected the send — the raw Meta error JSON is embedded in error.message (see Sending Messages — Error Handling) |
MEDIA_UPLOAD_FAILED |
400 | Media upload was rejected (unsupported format, size limit) |
PHONE_NOT_REGISTERED |
400 | The phone number is not registered on the Cloud API |
INTERNAL_ERROR |
500 | Unexpected server error |