LLM Integration Prompt

Copy the text below and paste it into your LLM (Claude, ChatGPT, etc.) when you need to implement the WhatsApp Cloud API integration in another product. It contains the full API surface, onboarding flow, webhook payloads, and message types — everything an LLM needs to write your integration code.


The Prompt

Paste everything below the line into your LLM conversation.


You are integrating with the WhatsApp Cloud API. This document describes the full API surface you need to implement against. Use the base URL and API key provided in the project's environment configuration.

Authentication

All API requests require a Bearer token:

Authorization: Bearer <API_KEY>

Error responses: 401 (missing/invalid key), 403 (no access).

Data Model

Organization
├── API Keys
├── Onboarding Links
├── Shared Inbox Links
├── Outgoing Webhooks
└── WABAs (WhatsApp Business Accounts)
    └── Phone Numbers
        └── Conversations
            └── Messages

IDs are UUID v4 (internal) or numeric strings (Meta IDs in webhook payloads).

Response Format

Success (single): { "data": { ... } } Success (list): { "data": [...], "meta": { "pagination": { "page": 1, "limit": 50, "total": 120, "hasMore": true } } } Error: { "error": { "message": "...", "code": "NOT_FOUND" } }

Error codes: VALIDATION_ERROR (400), UNAUTHORIZED (401), FORBIDDEN (403), NOT_FOUND (404), INTERNAL_ERROR (500).


Step 1: Connect a WhatsApp Number (Onboarding Links)

Onboarding links let you onboard end-users' WhatsApp numbers without building any frontend. You create a link via the API, send the hosted URL to your end-user, and they complete Meta's Embedded Signup in the browser. After completion, we redirect back to your app and fire a webhook event.

Flow

  1. Your Server → API: POST /v1/onboarding-links with {name, externalId, redirectUrl, webhookUrl?, webhookSecret?, webhookEvents?}
  2. API → Your Server: Returns {id, hostedUrl, status: "pending", ...}
  3. Your Server → End-User: Send the hostedUrl (email, in-app link, etc.)
  4. End-User → API: Opens hostedUrl in browser — serves the hosted signup page
  5. End-User → Meta: Clicks "Connect WhatsApp" — FB.login() popup for Embedded Signup
  6. Meta → End-User: Returns OAuth code after user completes signup
  7. End-User → API: POST /signup/:id/callback with the OAuth code
  8. API: Processes signup — exchanges code for tokens, sets up WABA & phone number
  9. API → Your Server: Fires phone_number.connected webhook event
  10. API → End-User: Redirects to redirectUrl?onboarding_link_id=x&external_id=y&status=success
  11. Your Server → API: GET /v1/onboarding-links/:id — fetch the completed result

Create an Onboarding Link

POST /v1/onboarding-links
{
  "name": "Acme Corp",
  "externalId": "customer-123",
  "redirectUrl": "https://myapp.com/whatsapp/callback",
  "webhookUrl": "https://myapp.com/webhooks/whatsapp",
  "webhookSecret": "whsec_my_secret_key_123",
  "webhookEvents": ["message.received", "message.sent"]
}
Field Type Required Description
name string Yes Display name shown in signup page
externalId string No Your internal identifier for this link
redirectUrl string No URL to redirect to after signup completes
webhookUrl string No URL for an outgoing webhook auto-registered when the phone connects
webhookSecret string No HMAC signing secret for the webhook (16-256 chars)
webhookEvents string[] No Event types for the webhook (required if webhookUrl is set)

When webhookUrl is provided, an outgoing webhook is automatically created and scoped to the new WABA once the phone number connects. This saves a separate POST /v1/outgoing-webhooks call.

Response (201):

{
  "data": {
    "id": "550e8400-e29b-41d4-a716-446655440000",
    "name": "Acme Corp",
    "status": "pending",
    "externalId": "customer-123",
    "redirectUrl": "https://myapp.com/whatsapp/callback",
    "hostedUrl": "https://api.example.com/signup/550e8400-...",
    "phoneNumberId": null,
    "phoneNumber": null,
    "wabaId": null,
    "isPhoneRegistered": null,
    "webhookUrl": "https://myapp.com/webhooks/whatsapp",
    "webhookSecret": "whsec_my_secret_key_123",
    "webhookEvents": ["message.received", "message.sent"],
    "createdAt": "2024-01-15T10:30:00.000Z",
    "completedAt": null
  }
}

Redirect After Completion

On success: redirectUrl?onboarding_link_id=x&external_id=customer-123&status=success On error: redirectUrl?onboarding_link_id=x&external_id=customer-123&status=error&error=brief+message

If no redirectUrl is set, the hosted page shows a success/error message inline.

Get Onboarding Link (Enriched)

GET /v1/onboarding-links/{id}

When completed:

{
  "data": {
    "id": "550e8400-e29b-41d4-a716-446655440000",
    "name": "Acme Corp",
    "status": "completed",
    "externalId": "customer-123",
    "redirectUrl": "https://myapp.com/whatsapp/callback",
    "hostedUrl": "https://api.example.com/signup/550e8400-...",
    "phoneNumberId": "660e8400-e29b-41d4-a716-446655440001",
    "phoneNumber": "+5491155551234",
    "wabaId": "770e8400-e29b-41d4-a716-446655440002",
    "isPhoneRegistered": true,
    "webhookUrl": "https://api.example.com/webhook/phone/660e8400-...",
    "createdAt": "2024-01-15T10:30:00.000Z",
    "completedAt": "2024-01-15T10:35:00.000Z"
  }
}

Use phoneNumberId from this response to identify the phone, then check readiness before sending traffic.

Check Phone Health / Readiness

GET /v1/phones/{phoneId}/health

Read the fields in data.capabilities:

This endpoint also triggers an asynchronous resync against Meta in the background. If local state was stale, a later call may return updated readiness values.

Other Onboarding Link Endpoints


Step 2: Set Up Webhooks

Register a webhook to get notified of incoming messages, status updates, and phone connections.

POST /v1/outgoing-webhooks
{
  "url": "https://yourapp.com/webhooks/whatsapp",
  "secret": "whsec_your_secret_key_min_16_chars",
  "events": ["message.received", "message.sent", "phone_number.connected"],
  "isActive": true
}
Field Type Required Description
url string Yes Your HTTPS endpoint URL
secret string No Signing secret for HMAC-SHA256 verification (16-256 chars)
events string[] Yes At least one event type
isActive boolean No Whether active (default: true)

Event Types

Event Description
message.received Message received from a contact
message.sent Message sent to a contact
message.status_updated Message status changed (delivered, read, failed)
phone_number.connected Phone number connected via onboarding
* Subscribe to all events

Note: phone_number.connected means onboarding finished. It does not guarantee the phone is operationally ready for traffic. Use GET /v1/phones/{phoneId}/health and check the relevant field in data.capabilities for that.


Optional: Shared Inbox Links

Shared inbox links let you expose one phone number's inbox through the hosted inbox frontend without giving the external user your organization API key.

Create one with:

POST /v1/shared-inbox-links
{
  "phoneNumberId": "phone-uuid",
  "name": "Support access",
  "expiresAt": null,
  "permissions": {
    "readInbox": true,
    "readDetails": true,
    "markRead": true,
    "sendText": true,
    "sendMedia": true,
    "sendTemplates": true,
    "sendAdvanced": false
  }
}

Response includes publicUrl. Send that URL to the external user. The frontend exchanges the public token into a short-lived shared session automatically.

Permission keys:

Useful endpoints:

Notes:

Webhook Payload

{
  "event": "message.received",
  "timestamp": "2026-03-03T15:30:00.000Z",
  "organizationId": "550e8400-e29b-41d4-a716-446655440000",
  "data": {
    "messageId": "msg-uuid",
    "conversationId": "conv-uuid",
    "phoneNumberId": "phone-uuid",
    "phoneNumber": "+15551234567",
    "contactPhone": "5491155551234",
    "contactName": "John Doe",
    "direction": "inbound",
    "type": "text",
    "content": { "body": "Hi, I'd like to place an order" },
    "wamid": "wamid.HBgLNTQ5MTE1NTU...",
    "status": "delivered",
    "externalId": "customer-123",
    "error": null
  }
}

The externalId field is present in all message events (message.received, message.sent, message.status_updated). It contains the externalId from the onboarding link that connected the phone, or null if not applicable.

Media messages: For inbound media messages (image, video, audio, document, sticker), the content object includes both a metadata URL and a direct download URL:

{
  "type": "image",
  "content": {
    "mediaId": "1234567890",
    "mediaUrl": "https://api.example.com/v1/phones/phone-uuid/media/1234567890",
    "downloadUrl": "https://api.example.com/v1/phones/phone-uuid/media/1234567890/download",
    "caption": "Photo of the product"
  }
}

Use downloadUrl to stream the actual file bytes.

Use mediaUrl when you need metadata (id, mimeType, fileSize, filename) plus the temporary Meta download URL.

Both endpoints require Authorization: Bearer <apiKey>.

phone_number.connected Payload

{
  "event": "phone_number.connected",
  "timestamp": "2024-01-15T10:35:00.000Z",
  "organizationId": "org_uuid",
  "data": {
    "onboardingLinkId": "link_uuid",
    "externalId": "customer-123",
    "phoneNumberId": "phone_uuid",
    "phoneNumber": "+5491155551234",
    "wabaId": "waba_uuid",
    "metaWabaId": "meta_waba_id",
    "isPhoneRegistered": true
  }
}

HTTP Headers on Every Delivery

Header Description
Content-Type application/json
User-Agent WhatsApp-CloudAPI-Webhook/1.0
X-Webhook-Event The event type
X-Webhook-Timestamp ISO 8601 timestamp
X-Webhook-Delivery Unique delivery ID (for deduplication)
X-Webhook-Signature-256 HMAC-SHA256 signature (only if secret is set)

Verifying Signatures

import { createHmac, timingSafeEqual } from "node:crypto";

function verifyWebhookSignature(rawBody, signatureHeader, secret) {
  const expected = createHmac("sha256", secret).update(rawBody).digest("hex");
  const received = signatureHeader.replace("sha256=", "");
  return timingSafeEqual(Buffer.from(expected, "hex"), Buffer.from(received, "hex"));
}

Your endpoint must return a 2xx status within 30 seconds.

Other Webhook Endpoints


Step 3: Send Messages

All message types use a single endpoint. The type field determines the payload shape.

POST /v1/phones/{phoneId}/messages

Common fields: type (string, required), to (string, required — recipient phone with country code).

Response: { "data": { "messageId": "uuid", "wamid": "wamid.HBgL..." } }

Text

{ "type": "text", "to": "5491155551234", "text": "Hello!", "previewUrl": false }

Image / Video / Audio / Document / Sticker

By URL:

{ "type": "image", "to": "5491155551234", "mediaUrl": "https://example.com/photo.jpg", "caption": "Check this out!" }

By pre-uploaded media ID:

{ "type": "document", "to": "5491155551234", "mediaId": "1234567890", "caption": "Report", "filename": "report.pdf" }

Either mediaId or mediaUrl must be provided, not both.

Upload media: POST /v1/phones/{phoneId}/media (multipart/form-data, file field) returns { "data": { "id": "1234567890" } }

Template

{
  "type": "template",
  "to": "5491155551234",
  "templateName": "order_confirmation",
  "languageCode": "en_US",
  "parameters": {
    "header": [{ "type": "text", "text": "Order #1234" }],
    "body": [{ "type": "text", "text": "John" }, { "type": "text", "text": "$99.00" }],
    "buttons": [{ "type": "button", "subType": "url", "index": 0, "parameters": [{ "type": "text", "text": "order-1234" }] }]
  }
}

Interactive Button

{
  "type": "interactive",
  "to": "5491155551234",
  "interactiveType": "button",
  "body": "How would you like to proceed?",
  "header": { "type": "text", "text": "Choose an option" },
  "footer": "Reply within 24 hours",
  "buttons": [
    { "id": "btn_buy", "title": "Buy Now" },
    { "id": "btn_info", "title": "More Info" }
  ]
}

Interactive List

{
  "type": "interactive",
  "to": "5491155551234",
  "interactiveType": "list",
  "body": "Browse our catalog:",
  "buttonText": "View Products",
  "sections": [
    { "title": "Electronics", "rows": [
      { "id": "phone_1", "title": "Smartphone X", "description": "Latest model" }
    ]}
  ]
}

CTA URL

{
  "type": "cta_url",
  "to": "5491155551234",
  "bodyText": "Visit our store",
  "buttonText": "Open Store",
  "url": "https://store.example.com",
  "headerText": "Special Offer",
  "footerText": "Offer expires soon"
}

Flow

{
  "type": "flow",
  "to": "5491155551234",
  "bodyText": "Complete your registration",
  "flowToken": "token_abc123",
  "flowId": "1234567890",
  "flowCta": "Start Registration",
  "flowAction": "navigate",
  "screen": "WELCOME_SCREEN",
  "data": { "userId": "usr_123" }
}

Mark as Read

POST /v1/phones/{phoneId}/messages/{wamid}/read
{ "showTypingIndicator": false }

Step 4: Read Conversations & Messages

List Conversations

GET /v1/phones/{phoneId}/conversations?page=1&limit=50
{
  "data": [
    {
      "id": "conv-uuid",
      "contactPhone": "5491155551234",
      "contactName": "John Doe",
      "lastMessageAt": "2026-03-10T14:30:00.000Z",
      "createdAt": "2026-03-01T10:00:00.000Z"
    }
  ],
  "meta": { "pagination": { "page": 1, "limit": 50, "total": 12, "hasMore": false } }
}

Get Messages

GET /v1/phones/{phoneId}/conversations/{conversationId}/messages?page=1&limit=50
{
  "data": [
    {
      "id": "msg-uuid",
      "wamid": "wamid.HBgLNTQ5MTE1NTU...",
      "direction": "inbound",
      "type": "text",
      "content": { "body": "Hi there!" },
      "status": "delivered",
      "timestamp": "2026-03-10T14:30:00.000Z"
    }
  ]
}

Message Statuses (outbound only)

pendingsentdeliveredread (or failed at any point)

Content by Message Type

Type Content
text { body }
image { url, caption?, mimeType? }
video { url, caption?, mimeType? }
audio { url, mimeType? }
document { url, caption?, filename? }
sticker { url }
location { latitude, longitude, name? }
contacts { contacts: [] }
interactive { type, ... } (button/list reply)
button { text, payload }
reaction { emoji }

Implementation Checklist

  1. Create onboarding linkPOST /v1/onboarding-links with redirectUrl pointing to your app. Optionally include webhookUrl, webhookSecret, and webhookEvents to auto-register a webhook when the phone connects.
  2. Display hosted URL — Show hostedUrl to end-users so they can connect their WhatsApp number
  3. Handle redirect — Parse onboarding_link_id, external_id, and status from redirect query params
  4. Handle webhook — Listen for phone_number.connected for server-to-server reliability
  5. Fetch resultGET /v1/onboarding-links/:id to get phoneNumberId, phoneNumber, wabaId
  6. Store phone number ID — Save phoneNumberId — this is the key for all messaging operations
  7. Register webhookPOST /v1/outgoing-webhooks to receive message.received events (skip if you used webhookUrl in step 1)
  8. Send messagesPOST /v1/phones/{phoneId}/messages with the appropriate type
  9. Verify webhook signatures — HMAC-SHA256 with your secret for security
  10. Read conversationsGET /v1/phones/{phoneId}/conversations + messages endpoint
  11. Handle media in webhooks — Use content.downloadUrl to download the file bytes, or content.mediaUrl to fetch metadata (both require Bearer auth)