The popup flow allows platform developers to embed WhatsApp account connection directly in their applications using Meta’s FB.login() popup. This provides a seamless in-app experience without full-page redirects.
This guide is for platform developers building applications on top of Chirp. If you just want to connect your own WhatsApp Business Account, use the dashboard method .
When to Use This Flow
Choose the popup flow when:
You want a seamless, in-app signup experience
You’re building a single-page application (SPA)
You need fine-grained control over the signup process
You want to track signup attempts with custom metadata
You’re building a multi-tenant platform where multiple end-users connect their accounts
For a simpler server-side redirect pattern, see the Redirect Flow .
How It Works
Start Attempt - Create a tracking record with optional metadata
Launch Popup - Open Meta’s Embedded Signup using FB.login()
Handle Events - Process FINISH, CANCEL, or ERROR events from Meta
Mark Interrupted - If the user completes signup, associate the WABA ID
Claim Profile - Create the WhatsApp Business Profile from a recovered attempt
Starting a Signup Attempt
Before launching the Meta Embedded Signup popup, create a tracking record:
curl -X POST https://api.buildwithchirp.com/v1/organization/whatsapp/business-profiles/start-attempt \
-H "Authorization: Bearer YOUR_ADMIN_KEY" \
-H "Content-Type: application/json" \
-d '{
"metadata": {
"externalUserId": "your_user_123",
"businessName": "Acme Corp",
"plan": "enterprise"
}
}'
Response:
{
"attemptId" : "wss_abc123..." ,
"expiresAt" : "2024-01-15T12:15:00.000Z"
}
The metadata field is optional but recommended for platforms with multiple end-users. Store your internal identifiers to later identify which user each recovered signup belongs to.
Use metadata to store any information that helps you identify your users:
Field Example Description externalUserId"user_123"Your internal user ID businessName"Acme Corp"Business name for display plan"enterprise"Customer tier or plan region"us-east"Geographic region
After creating the attempt, launch Meta’s Embedded Signup using the Facebook SDK:
Launch Embedded Signup Popup
// Initialize Facebook SDK first
FB . login (
( response ) => {
// Handle response - see "Handling Meta Events" below
},
{
config_id: 'YOUR_FACEBOOK_LOGIN_CONFIG_ID' ,
response_type: 'code' ,
override_default_response_type: true ,
extras: {
setup: {
// Optional: pre-fill business information
business: {
name: 'Acme Corp' ,
email: '[email protected] '
}
},
featureType: '' ,
sessionInfoVersion: '3'
}
}
);
Meta’s Embedded Signup fires different events based on user actions:
FINISH Event
The user successfully completed the signup flow:
FB . Event . subscribe ( 'messenger_checkbox' , async ( event ) => {
if ( event . event === 'FINISH' ) {
const wabaId = event . data . waba_id ;
const phoneNumberId = event . data . phone_number_id ;
// Mark the attempt with the WABA ID
await chirp . admin . whatsapp . businessProfiles . markInterrupted ({
attemptId: attemptId ,
metaWabaId: wabaId
});
// Complete the signup via callback
await chirp . admin . whatsapp . businessProfiles . callback ({
code: response . authResponse . code ,
wabaId: wabaId ,
phoneNumberId: phoneNumberId
});
}
});
CANCEL Event
The user closed the popup without completing:
if ( event . event === 'CANCEL' ) {
// Optionally track the cancellation
await chirp . admin . whatsapp . businessProfiles . cancelAttempt ({
attemptId: attemptId ,
currentStep: event . data ?. current_step ,
errorMessage: 'User closed the dialog'
});
}
ERROR Event
Meta encountered an error during signup:
if ( event . event === 'ERROR' ) {
await chirp . admin . whatsapp . businessProfiles . failAttempt ({
attemptId: attemptId ,
errorMessage: event . data ?. error_message ,
errorId: event . data ?. error_id ,
sessionId: event . data ?. session_id ,
currentStep: event . data ?. current_step
});
}
Marking an Interrupted Signup
When a user completes the Meta signup but something prevents the normal flow from completing (network error, browser tab closed, etc.), use the mark-interrupted endpoint to associate the WABA ID with the attempt:
curl -X POST https://api.buildwithchirp.com/v1/organization/whatsapp/business-profiles/mark-interrupted \
-H "Authorization: Bearer YOUR_ADMIN_KEY" \
-H "Content-Type: application/json" \
-d '{
"attemptId": "wss_abc123...",
"metaWabaId": "123456789"
}'
This ensures the attempt can be recovered later even if the callback fails.
Handling Recovered Signups
If a user completes the Meta signup but doesn’t return to your application, the signup is marked for recovery. Query recovered attempts to show a “Continue Setup” UI:
curl -X GET https://api.buildwithchirp.com/v1/organization/whatsapp/business-profiles/recovered-attempts \
-H "Authorization: Bearer YOUR_ADMIN_KEY"
Response:
Recovered Attempts Response
{
"recoveredAttempts" : [
{
"attemptId" : "wss_abc123..." ,
"metaWabaId" : "123456789" ,
"recoveredAt" : "2024-01-15T12:10:00.000Z" ,
"createdAt" : "2024-01-15T12:00:00.000Z" ,
"metadata" : {
"externalUserId" : "your_user_123" ,
"businessName" : "Acme Corp"
}
}
],
"totalCount" : 1
}
Filtering by Your Users
Use the returned metadata to filter recovered attempts for specific users:
Filter Recovered Attempts
import ChirpSDK from "@buildwithchirp/sdk" ;
const chirp = new ChirpSDK ({ apiKey: "YOUR_ADMIN_KEY" });
const { recoveredAttempts } = await chirp . admin . whatsapp . businessProfiles . listRecoveredAttempts ();
// Filter for a specific user
const userAttempts = recoveredAttempts . filter (
attempt => attempt . metadata ?. externalUserId === 'your_user_123'
);
if ( userAttempts . length > 0 ) {
// Show "Continue WhatsApp Setup" UI to this user
}
Claiming a Recovered Attempt
When a user is ready to complete their interrupted signup, claim the recovered attempt:
curl -X POST https://api.buildwithchirp.com/v1/organization/whatsapp/business-profiles/claim \
-H "Authorization: Bearer YOUR_ADMIN_KEY" \
-H "Content-Type: application/json" \
-d '{
"attemptId": "wss_abc123..."
}'
Response:
{
"id" : "wabp_xyz789..." ,
"metaWabaId" : "123456789" ,
"businessName" : "Acme Corp" ,
"phoneNumbers" : [
{
"id" : "wapn_abc..." ,
"displayPhoneNumber" : "+15551234567" ,
"metaPhoneNumberId" : "987654321" ,
"verified" : true
}
],
"createdAt" : "2024-01-15T12:15:00.000Z"
}
Cancelling an Attempt
If a user abandons the signup flow before completing it on Meta’s side:
curl -X POST https://api.buildwithchirp.com/v1/organization/whatsapp/business-profiles/cancel-attempt \
-H "Authorization: Bearer YOUR_ADMIN_KEY" \
-H "Content-Type: application/json" \
-d '{
"attemptId": "wss_abc123...",
"currentStep": "PHONE_NUMBER_VERIFICATION",
"errorMessage": "User closed the dialog"
}'
This is optional but helps keep your recovered attempts list clean and provides analytics on where users drop off.
If Meta returns an error during the signup flow, track it separately for analytics:
curl -X POST https://api.buildwithchirp.com/v1/organization/whatsapp/business-profiles/fail-attempt \
-H "Authorization: Bearer YOUR_ADMIN_KEY" \
-H "Content-Type: application/json" \
-d '{
"attemptId": "wss_abc123...",
"errorMessage": "Phone number verification failed",
"errorId": "100032",
"sessionId": "fb_session_xyz",
"currentStep": "PHONE_NUMBER_VERIFICATION"
}'
Error Tracking Fields
Field Description errorMessageHuman-readable error message from Meta (max 1000 chars) errorIdMeta error identifier for support ticket correlation (max 100 chars) sessionIdMeta session identifier for debugging (max 100 chars) currentStepStep in the signup flow where the error occurred (max 100 chars)
Distinguishing between user cancellations (cancel-attempt) and Meta errors (fail-attempt) enables better analytics. You can track conversion rates, identify problematic steps, and correlate with Meta support tickets using the errorId.
Complete Integration Example
Here’s a full example using the SDK:
Complete Popup Integration with SDK
import ChirpSDK from "@buildwithchirp/sdk" ;
class WhatsAppSignup {
private chirp : ChirpSDK ;
private attemptId : string | null = null ;
constructor ( apiKey : string ) {
this . chirp = new ChirpSDK ({ apiKey });
}
async startSignup ( metadata : Record < string , string > = {}) {
// 1. Create tracking attempt
const attempt = await this . chirp . admin . whatsapp . businessProfiles . startAttempt ({
metadata
});
this . attemptId = attempt . attemptId ;
// 2. Launch Meta popup
return new Promise (( resolve , reject ) => {
FB . login (
async ( response ) => {
if ( response . authResponse ) {
resolve ( response );
} else {
reject ( new Error ( 'User cancelled' ));
}
},
{
config_id: 'YOUR_CONFIG_ID' ,
response_type: 'code' ,
override_default_response_type: true
}
);
});
}
async handleFinish ( wabaId : string , code : string ) {
if ( ! this . attemptId ) throw new Error ( 'No active attempt' );
// Mark interrupted first (safety net)
await this . chirp . admin . whatsapp . businessProfiles . markInterrupted ({
attemptId: this . attemptId ,
metaWabaId: wabaId
});
// Complete via callback
return this . chirp . admin . whatsapp . businessProfiles . callback ({
code ,
wabaId
});
}
async handleCancel ( currentStep ?: string ) {
if ( ! this . attemptId ) return ;
await this . chirp . admin . whatsapp . businessProfiles . cancelAttempt ({
attemptId: this . attemptId ,
currentStep ,
errorMessage: 'User cancelled signup'
});
}
async handleError ( error : { message ?: string ; id ?: string ; sessionId ?: string ; step ?: string }) {
if ( ! this . attemptId ) return ;
await this . chirp . admin . whatsapp . businessProfiles . failAttempt ({
attemptId: this . attemptId ,
errorMessage: error . message ,
errorId: error . id ,
sessionId: error . sessionId ,
currentStep: error . step
});
}
async getRecoveredAttempts ( userId ?: string ) {
const { recoveredAttempts } = await this . chirp . admin . whatsapp . businessProfiles . listRecoveredAttempts ();
if ( userId ) {
return recoveredAttempts . filter ( a => a . metadata ?. externalUserId === userId );
}
return recoveredAttempts ;
}
async claimAttempt ( attemptId : string ) {
return this . chirp . admin . whatsapp . businessProfiles . claimRecoveredAttempt ( attemptId );
}
}
// Usage
const signup = new WhatsAppSignup ( 'YOUR_ADMIN_KEY' );
// Start signup for a user
await signup . startSignup ({ externalUserId: 'user_123' , businessName: 'Acme Corp' });
// Handle FB events...
Timing and Expiration
Signup attempts expire after 15 minutes. If a user doesn’t complete the Meta flow within this window, they’ll need to start a new signup attempt.
Next Steps