> ## Documentation Index
> Fetch the complete documentation index at: https://docs.buildwithchirp.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Embedded Signup: Popup Flow

> Integrate WhatsApp account connection using Meta's client-side popup

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.

<Info>
  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](/whatsapp/embedded-signup).
</Info>

## 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](/whatsapp/embedded-signup-redirect).

## 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

```mermaid theme={null}
sequenceDiagram
    participant User
    participant Your App
    participant Chirp API
    participant Meta

    User->>Your App: Click "Connect WhatsApp"
    Your App->>Chirp API: POST /start-attempt (with metadata)
    Chirp API-->>Your App: { attemptId, expiresAt }
    Your App->>Meta: FB.login() popup
    User->>Meta: Complete signup

    alt User completes successfully
        Meta->>Your App: FINISH event with wabaId
        Your App->>Chirp API: POST /mark-interrupted
        Your App->>Chirp API: POST /callback
        Chirp API-->>Your App: Business Profile created
    else User cancels
        Meta->>Your App: CANCEL event
        Your App->>Chirp API: POST /cancel-attempt
    else Meta error
        Meta->>Your App: ERROR event
        Your App->>Chirp API: POST /fail-attempt
    end
```

## Starting a Signup Attempt

Before launching the Meta Embedded Signup popup, create a tracking record:

<CodeGroup>
  ```bash cURL theme={null}
  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"
      }
    }'
  ```

  ```typescript SDK theme={null}
  import ChirpSDK from "@buildwithchirp/sdk";

  const chirp = new ChirpSDK({ apiKey: "YOUR_ADMIN_KEY" });

  const attempt = await chirp.admin.whatsapp.businessProfiles.startAttempt({
    metadata: {
      externalUserId: "your_user_123",
      businessName: "Acme Corp",
      plan: "enterprise"
    }
  });

  // attempt.attemptId, attempt.expiresAt
  ```
</CodeGroup>

**Response:**

```json icon="code" title="Attempt Response" theme={null}
{
  "attemptId": "wss_abc123...",
  "expiresAt": "2024-01-15T12:15:00.000Z"
}
```

<Info>
  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.
</Info>

### Metadata Use Cases

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         |

## Launching the Meta Popup

After creating the attempt, launch Meta's Embedded Signup using the Facebook SDK:

```javascript icon="javascript" title="Launch Embedded Signup Popup" theme={null}
// 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: 'contact@acme.com'
        }
      },
      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:

```javascript icon="javascript" title="Handle FINISH Event" theme={null}
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:

```javascript icon="javascript" title="Handle CANCEL Event" theme={null}
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:

```javascript icon="javascript" title="Handle ERROR Event" theme={null}
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:

<CodeGroup>
  ```bash cURL theme={null}
  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"
    }'
  ```

  ```typescript SDK theme={null}
  import ChirpSDK from "@buildwithchirp/sdk";

  const chirp = new ChirpSDK({ apiKey: "YOUR_ADMIN_KEY" });

  await chirp.admin.whatsapp.businessProfiles.markInterrupted({
    attemptId: "wss_abc123...",
    metaWabaId: "123456789"
  });
  ```
</CodeGroup>

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:

<CodeGroup>
  ```bash cURL theme={null}
  curl -X GET https://api.buildwithchirp.com/v1/organization/whatsapp/business-profiles/recovered-attempts \
    -H "Authorization: Bearer YOUR_ADMIN_KEY"
  ```

  ```typescript SDK theme={null}
  import ChirpSDK from "@buildwithchirp/sdk";

  const chirp = new ChirpSDK({ apiKey: "YOUR_ADMIN_KEY" });

  const { recoveredAttempts } = await chirp.admin.whatsapp.businessProfiles.listRecoveredAttempts();
  ```
</CodeGroup>

**Response:**

```json icon="code" title="Recovered Attempts Response" theme={null}
{
  "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:

```typescript icon="typescript" title="Filter Recovered Attempts" theme={null}
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:

<CodeGroup>
  ```bash cURL theme={null}
  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..."
    }'
  ```

  ```typescript SDK theme={null}
  import ChirpSDK from "@buildwithchirp/sdk";

  const chirp = new ChirpSDK({ apiKey: "YOUR_ADMIN_KEY" });

  const profile = await chirp.admin.whatsapp.businessProfiles.claimRecoveredAttempt(
    "wss_abc123..."
  );

  // profile.id, profile.metaWabaId, profile.phoneNumbers
  ```
</CodeGroup>

**Response:**

```json icon="code" title="Claim Response" theme={null}
{
  "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:

<CodeGroup>
  ```bash cURL theme={null}
  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"
    }'
  ```

  ```typescript SDK theme={null}
  import ChirpSDK from "@buildwithchirp/sdk";

  const chirp = new ChirpSDK({ apiKey: "YOUR_ADMIN_KEY" });

  await chirp.admin.whatsapp.businessProfiles.cancelAttempt({
    attemptId: "wss_abc123...",
    currentStep: "PHONE_NUMBER_VERIFICATION",
    errorMessage: "User closed the dialog"
  });
  ```
</CodeGroup>

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:

<CodeGroup>
  ```bash cURL theme={null}
  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"
    }'
  ```

  ```typescript SDK theme={null}
  import ChirpSDK from "@buildwithchirp/sdk";

  const chirp = new ChirpSDK({ apiKey: "YOUR_ADMIN_KEY" });

  await chirp.admin.whatsapp.businessProfiles.failAttempt({
    attemptId: "wss_abc123...",
    errorMessage: "Phone number verification failed",
    errorId: "100032",
    sessionId: "fb_session_xyz",
    currentStep: "PHONE_NUMBER_VERIFICATION"
  });
  ```
</CodeGroup>

### Error Tracking Fields

| Field          | Description                                                          |
| -------------- | -------------------------------------------------------------------- |
| `errorMessage` | Human-readable error message from Meta (max 1000 chars)              |
| `errorId`      | Meta error identifier for support ticket correlation (max 100 chars) |
| `sessionId`    | Meta session identifier for debugging (max 100 chars)                |
| `currentStep`  | Step in the signup flow where the error occurred (max 100 chars)     |

<Info>
  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`.
</Info>

## Complete Integration Example

Here's a full example using the SDK:

```typescript icon="typescript" title="Complete Popup Integration with SDK" theme={null}
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

<Warning>
  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.
</Warning>

## Next Steps

<CardGroup cols={2}>
  <Card title="API Reference" icon="book" href="/api_reference/introduction">
    Full API documentation for signup attempt tracking
  </Card>

  <Card title="Redirect Flow" icon="arrow-right-arrow-left" href="/whatsapp/embedded-signup-redirect">
    Alternative server-side redirect integration
  </Card>
</CardGroup>
