Skip to main content
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

  1. Start Attempt - Create a tracking record with optional metadata
  2. Launch Popup - Open Meta’s Embedded Signup using FB.login()
  3. Handle Events - Process FINISH, CANCEL, or ERROR events from Meta
  4. Mark Interrupted - If the user completes signup, associate the WABA ID
  5. 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:
Attempt 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.

Metadata Use Cases

Use metadata to store any information that helps you identify your users:
FieldExampleDescription
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

Launching the Meta Popup

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'
    }
  }
);

Handling Meta Events

Meta’s Embedded Signup fires different events based on user actions:

FINISH Event

The user successfully completed the signup flow:
Handle FINISH Event
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:
Handle CANCEL Event
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:
Handle ERROR Event
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:
Claim 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.

Tracking Meta Errors

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

FieldDescription
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