# Onboarding Clients via API

> Programmatically create partner invitations to let clients connect their WhatsApp Business account or Facebook Page

This guide shows you how to automate client onboarding using the Zavu API to create partner invitations programmatically.

## Overview

The Partner Invitations API allows you to:
- Create invitation links programmatically
- Onboard a client on WhatsApp (`whatsapp_waba`) or Messenger (`messenger`)
- Integrate client onboarding into your own application
- Optionally pre-assign phone numbers to WhatsApp invitations
- Track invitation status and completion

### Choosing a channel

`connectionType` decides what the client connects. It defaults to `whatsapp_waba`, so existing integrations keep working unchanged.

| `connectionType` | Client authorizes | Result |
|---|---|---|
| `whatsapp_waba` | Meta's embedded signup | A sender with the WhatsApp channel |
| `messenger` | Facebook, then picks a Page they administer | A sender with the Messenger channel, receiving Page and Marketplace chats |

One invitation connects one channel. To onboard a client on both, create two invitations; each completes into its own sender.

## Creating an Invitation

### Basic Invitation

Create an invitation where the client provides their own phone number:

```typescript
import Zavudev from '@zavudev/sdk';

const zavu = new Zavudev({
  apiKey: process.env["ZAVUDEV_API_KEY"],
});

const invitation = await zavu.invitations.create({
  clientName: 'Acme Corp',
  clientEmail: 'contact@acme.com',
  expiresInDays: 7,
});

console.log('Invitation URL:', invitation.url);
console.log('Token:', invitation.token);
console.log('Expires:', invitation.expiresAt);
```

```bash
curl -X POST https://api.zavu.dev/v1/invitations \
  -H "Authorization: Bearer $ZAVUDEV_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "clientName": "Acme Corp",
    "clientEmail": "contact@acme.com",
    "expiresInDays": 7
  }'
```

### Response

```json
{
  "invitation": {
    "id": "jh7am5bng9p3v2x1k4r8",
    "url": "https://dashboard.zavu.dev/invite/abc123xyz",
    "token": "abc123xyz",
    "clientName": "Acme Corp",
    "clientEmail": "contact@acme.com",
    "phoneNumberId": null,
    "status": "pending",
    "senderId": null,
    "expiresAt": "2025-01-22T00:00:00.000Z",
    "createdAt": "2025-01-15T12:00:00.000Z"
  }
}
```

### With Pre-assigned Phone Number

Pre-assign a Zavu phone number that the client will register with WhatsApp:

```typescript
// First, purchase a phone number
const phoneNumber = await zavu.phoneNumbers.purchase({
  phoneNumber: '+14155551234',
  name: 'Client: Acme Corp',
});

// Then create the invitation with the phone number
const invitation = await zavu.invitations.create({
  clientName: 'Acme Corp',
  clientEmail: 'contact@acme.com',
  phoneNumberId: phoneNumber.id,
  expiresInDays: 14,
});

console.log('Invitation URL:', invitation.url);
```

```bash
# First, purchase a phone number
curl -X POST https://api.zavu.dev/v1/phone-numbers \
  -H "Authorization: Bearer $ZAVUDEV_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "phoneNumber": "+14155551234",
    "name": "Client: Acme Corp"
  }'

# Then create the invitation with the phone number ID
curl -X POST https://api.zavu.dev/v1/invitations \
  -H "Authorization: Bearer $ZAVUDEV_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "clientName": "Acme Corp",
    "clientEmail": "contact@acme.com",
    "phoneNumberId": "pn_abc123",
    "expiresInDays": 14
  }'
```

> **Info:**
> When you pre-assign a phone number, the invitation page will show the number and automatically display the verification code when it's received via phone call.

### Messenger Invitation

Create an invitation for a Facebook Page. There is no phone number involved, so the WhatsApp-only options are rejected with `400`.

```typescript
const { invitation } = await zavu.invitations.create({
  clientName: 'Acme Corp',
  clientEmail: 'contact@acme.com',
  connectionType: 'messenger',
});

console.log(invitation.url);
```

```bash
curl -X POST https://api.zavu.dev/v1/invitations \
  -H "Authorization: Bearer $ZAVUDEV_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "clientName": "Acme Corp",
    "clientEmail": "contact@acme.com",
    "connectionType": "messenger"
  }'
```

> **Note:**
> A Facebook Page can only be connected to one Zavu project at a time. If the client picks a Page that another project already connected, the newer connection wins and the older one is disconnected.

### Create Parameters

| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `clientName` | string | No | Name to identify the client (max 100 chars) |
| `clientEmail` | string | No | Client's email address |
| `clientPhone` | string | No | Client's phone number (E.164 format) |
| `connectionType` | string | No | `whatsapp_waba` (default) or `messenger` |
| `phoneNumberId` | string | No | Pre-assign a Zavu phone number. `whatsapp_waba` only |
| `allowedPhoneCountries` | string[] | No | ISO country codes for allowed numbers. `whatsapp_waba` only |
| `expiresInDays` | integer | No | Link validity in days (1-30, default: 7) |

## Listing Invitations

Retrieve all invitations for your project:

```typescript
// List all invitations
const { items } = await zavu.invitations.list({});

// Filter by status
const pending = await zavu.invitations.list({
  status: 'pending',
});

for (const inv of pending.items) {
  console.log(inv.clientName, inv.status, inv.url);
}
```

```bash
# List all invitations
curl "https://api.zavu.dev/v1/invitations" \
  -H "Authorization: Bearer $ZAVUDEV_API_KEY"

# Filter by status
curl "https://api.zavu.dev/v1/invitations?status=pending" \
  -H "Authorization: Bearer $ZAVUDEV_API_KEY"
```

### List Parameters

| Parameter | Type | Description |
|-----------|------|-------------|
| `status` | string | Filter by status: `pending`, `in_progress`, `completed`, `expired`, `cancelled` |
| `limit` | integer | Results per page (default: 50, max: 100) |
| `cursor` | string | Pagination cursor |

## Getting Invitation Details

Retrieve details for a specific invitation:

```typescript
const invitation = await zavu.invitations.retrieve({
  invitationId: 'jh7am5bng9p3v2x1k4r8',
});

console.log('Status:', invitation.status);
console.log('Client:', invitation.clientName);

if (invitation.status === 'completed') {
  console.log('Sender ID:', invitation.senderId);
}
```

```bash
curl "https://api.zavu.dev/v1/invitations/jh7am5bng9p3v2x1k4r8" \
  -H "Authorization: Bearer $ZAVUDEV_API_KEY"
```

### Response Fields

| Field | Type | Description |
|-------|------|-------------|
| `id` | string | Unique invitation ID |
| `url` | string | Full invitation URL to share with client |
| `token` | string | URL token (part of the URL) |
| `clientName` | string | Client name |
| `clientEmail` | string | Client email |
| `phoneNumberId` | string | Pre-assigned phone number ID (if any) |
| `connectionType` | string | `whatsapp_waba` or `messenger` |
| `status` | string | Current status |
| `senderId` | string | Created sender ID (when completed) |
| `connectedAccount` | object | What was linked, once completed: `{ channel, id, name }`. The WhatsApp number, or the Facebook Page. `null` before completion |
| `failureReason` | string | Why the last attempt failed, when `status` is `failed` |
| `failedAt` | string | When the last attempt failed |
| `expiresAt` | string | Expiration timestamp |
| `viewedAt` | string | When client first viewed the invitation |
| `startedAt` | string | When client started the setup |
| `completedAt` | string | When setup was completed |

## Cancelling an Invitation

Cancel an invitation to prevent it from being used:

```typescript
await zavu.invitations.cancel({
  invitationId: 'jh7am5bng9p3v2x1k4r8',
});
```

```bash
curl -X POST "https://api.zavu.dev/v1/invitations/jh7am5bng9p3v2x1k4r8/cancel" \
  -H "Authorization: Bearer $ZAVUDEV_API_KEY"
```

> **Note:**
> You cannot cancel a completed invitation. Once a sender is created, manage it through the Senders API instead.

## Invitation Lifecycle

```mermaid
stateDiagram-v2
    [*] --> pending: Create invitation
    pending --> in_progress: Client starts setup
    pending --> cancelled: Cancel invitation
    in_progress --> completed: Setup successful
    in_progress --> failed: Client cancelled, denied, or abandoned
    in_progress --> cancelled: Cancel invitation
    failed --> in_progress: Client retries the same link
    failed --> cancelled: Cancel invitation
    failed --> pending: Resend from the dashboard
    cancelled --> pending: Resend from the dashboard
    completed --> [*]: Sender created
```

A `failed` invitation is not dead: the link stays usable and moves back to `in_progress` when the client tries again. `failureReason` carries a stable code — `fb_cancelled`, `fb_not_authorized`, `signup_abandoned`, `meta_no_pages` (the client administers no Facebook Page), and others. Treat unknown codes as a generic failure.

A client who starts the connection and does not finish within an hour is marked `failed` with `signup_abandoned`.

**Expiry is not a transition.** A `pending` invitation past `expiresAt` is reported as `expired` when you read it, and its link stops working, but its stored status never changes. That means no `invitation.status_changed` event is sent when an invitation expires. Resend it from the dashboard, or create a new one.

Every arrow above except expiry sends an `invitation.status_changed` event. See [Webhook Integration](#webhook-integration).

## Integration Examples

### SaaS Onboarding Flow

Integrate WhatsApp setup into your SaaS onboarding:

```typescript
async function onboardNewCustomer(customer: Customer) {
  // 1. Create the customer in your system
  await createCustomer(customer);

  // 2. Create a Zavu invitation
  const invitation = await zavu.invitations.create({
    clientName: customer.companyName,
    clientEmail: customer.email,
    expiresInDays: 14,
  });

  // 3. Send onboarding email with the invitation link
  await sendEmail({
    to: customer.email,
    subject: 'Connect Your WhatsApp Business',
    body: `
      Welcome to our platform!

      To enable WhatsApp messaging, please complete the setup:
      ${invitation.url}

      This link expires in 14 days.
    `,
  });

  // 4. Store the invitation ID for tracking
  await saveInvitationId(customer.id, invitation.id);
}
```

### Polling for Completion

Check if a client has completed the setup:

```typescript
async function checkInvitationStatus(invitationId: string) {
  const invitation = await zavu.invitations.retrieve({ invitationId });

  switch (invitation.status) {
    case 'completed':
      console.log('Client connected! Sender ID:', invitation.senderId);
      // Start sending messages through this sender
      break;
    case 'pending':
    case 'in_progress':
      console.log('Still waiting for client to complete setup');
      break;
    case 'expired':
      console.log('Invitation expired, creating new one...');
      // Create a new invitation
      break;
    case 'cancelled':
      console.log('Invitation was cancelled');
      break;
  }

  return invitation;
}
```

### Webhook Integration

Subscribe your project webhook to `invitation.status_changed` to follow each client without polling. Configure it on the project whose API key creates the invitations: with sub-accounts, that is each sub-account, since a parent project does not receive its sub-accounts' events.

```bash
curl -X POST https://api.zavu.dev/v1/invitations/webhook \
  -H "Authorization: Bearer $ZAVUDEV_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "url": "https://your-app.com/webhooks/zavu", "events": ["invitation.status_changed"] }'
```

```typescript
// In your webhook handler. Verify the X-Zavu-Signature header first.
app.post('/webhooks/zavu', async (req, res) => {
  const event = req.body;

  if (event.type === 'invitation.status_changed') {
    const { invitationId, currentStatus, senderId, connectedAccount, failureReason } = event.data;

    if (currentStatus === 'completed') {
      // The client is connected; senderId is ready to send with
      await markCustomerConnected(invitationId, senderId, connectedAccount);
    } else if (currentStatus === 'failed') {
      // The same link still works, so a reminder is usually enough
      await remindCustomer(invitationId, failureReason);
    }
  }

  res.status(200).send('OK');
});
```

See [the event reference](https://www.zavu.dev/docs/guides/receiving-messages/events) for every field and transition.

## Error Handling

```typescript
try {
  const invitation = await zavu.invitations.create({
    clientName: 'Acme Corp',
    phoneNumberId: 'pn_invalid',
  });
} catch (error) {
  if (error.code === 'not_found') {
    console.error('Phone number not found');
  } else if (error.code === 'invalid_request') {
    console.error('Invalid request:', error.message);
  } else {
    console.error('Unexpected error:', error);
  }
}
```

### Common Errors

| Code | Description |
|------|-------------|
| `not_found` | Phone number or invitation not found |
| `invalid_request` | Invalid parameters |
| `phone_number_wrong_team` | Phone number belongs to a different team |
| `already_completed` | Cannot cancel a completed invitation |

## Best Practices

  - **Store Invitation IDs**: Save the invitation ID in your database to track status and link to your customer records.
  - **Set Reminders**: If an invitation is pending for several days, send a reminder to your client.
  - **Handle Expiration**: Expiry sends no event. Check `expiresAt` on pending invitations and resend or recreate them.
  - **Use Webhooks**: Subscribe to `invitation.status_changed` to know the moment a client connects or fails.

## Rate Limits

The Invitations API has the following rate limits:

| Endpoint | Limit |
|----------|-------|
| Create invitation | 100 requests/minute |
| List invitations | 200 requests/minute |
| Get invitation | 200 requests/minute |
| Cancel invitation | 100 requests/minute |

## Next Steps

  - [Dashboard Guide](https://www.zavu.dev/docs/guides/partner-invitations/dashboard): Learn to create invitations from the dashboard
  - [Senders API](https://www.zavu.dev/docs/concepts/senders): Work with senders created from invitations
  - [Send WhatsApp Messages](https://www.zavu.dev/docs/guides/whatsapp/overview): Start sending messages through your new senders
  - [Phone Numbers](https://www.zavu.dev/docs/guides/phone-numbers/purchasing): Purchase phone numbers to pre-assign to invitations
