Onboarding Links
Onboarding links let API-only customers onboard their end-users' WhatsApp numbers without building any frontend. You create a link, 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.
Inspired by Stripe Connect and Plaid Hosted Link.
Flow
POST /v1/onboarding-links with {name, externalId, redirectUrl, webhookUrl?, webhookSecret?, webhookEvents?}{id, hostedUrl, status: "pending", ...}hostedUrl to your end-user (email, in-app link, etc.)hostedUrl in the browser — serves the hosted signup pageFB.login() popup opens for Embedded SignupPOST /signup/:id/callback with the OAuth codephone_number.connected webhook eventredirectUrl?onboarding_link_id=x&external_id=y&status=successGET /v1/onboarding-links/:id — fetch the completed result with phoneNumberId, phoneNumber, wabaIdCreate an Onboarding Link
curl -X POST https://api.example.com/v1/onboarding-links \
-H "Authorization: Bearer sk_org_..." \
-H "Content-Type: application/json" \
-d '{
"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 the 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
}
}
Send the hostedUrl to your end-user (email, in-app link, etc.). They open it in a browser to start the signup flow.
Redirect After Completion
If you provided a redirectUrl, the end-user is redirected after signup:
On success:
https://myapp.com/whatsapp/callback?onboarding_link_id=x&external_id=customer-123&status=success
On error:
https://myapp.com/whatsapp/callback?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)
After the redirect (or webhook), fetch the full result:
curl https://api.example.com/v1/onboarding-links/{id} \
-H "Authorization: Bearer sk_org_..."
Response 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 the phoneNumberId from this response to send messages.
Before sending production traffic, call GET /v1/phones/{phoneNumberId}/health and check the relevant field in data.capabilities. A phone can be connected and even registered, but still not be fully ready if Meta reports health or subscription issues.
Example:
curl https://api.example.com/v1/phones/{phoneNumberId}/health \
-H "Authorization: Bearer sk_org_..."
For inbound-first use cases, treat data.capabilities.canReceiveMessages: true as the platform's current "safe to use" signal.
Webhook Event: phone_number.connected
For server-to-server reliability, subscribe to the phone_number.connected event via Outgoing Webhooks:
{
"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
}
}
List Onboarding Links
curl "https://api.example.com/v1/onboarding-links?page=1&limit=50" \
-H "Authorization: Bearer sk_org_..."
Delete an Onboarding Link
Only pending links can be deleted.
curl -X DELETE https://api.example.com/v1/onboarding-links/{id} \
-H "Authorization: Bearer sk_org_..."