# WhatsApp for Partners

# Partner API and Embed Integration Guide

<div style="border: 1px solid #d7d7d7; background: #f7f7f4; margin: 0 0 24px 0; overflow: hidden;">
  <table style="width: 100%; border-collapse: collapse; margin: 0; background: #f7f7f4;">
    <tbody>
      <tr style="border-bottom: 1px solid #e5e5e0;">
        <td style="padding: 14px 24px; color: #666; font-size: 18px; line-height: 1.3;">Agent instruction</td>
        <td style="padding: 14px 24px; color: #666; font-size: 18px; line-height: 1.3; text-align: right;">Copy</td>
      </tr>
    </tbody>
  </table>
  <pre style="margin: 0; padding: 22px 24px; background: #f7f7f4; color: #00112b; font-family: Consolas, 'Liberation Mono', Menlo, monospace; font-size: 18px; line-height: 1.45; white-space: pre-wrap;"><code>Implement ChatArchitect WhatsApp Partner embedded registration in this codebase following the official guide:
https://support.chatarchitect.com/books/whatsapp-for-partners/page/partner-api-and-embed-integration-guide.
First inspect the existing frontend/backend/env/storage patterns, then add the backend session creation endpoint,
frontend iframe flow with postMessage handling and signed webhook verification/storage.
Keep all Partner API calls server-side, never expose partner token to browser code.</code></pre>
</div>

This guide explains how a partner backend creates a WhatsApp registration session, embeds the registration UI in an iframe, receives completion events, and retrieves final app credentials.

## Overview

The integration is backend-first:

1. Your backend calls the Partner API to create a registration session.
2. The API returns a `registration_id` and an `iframe_url`.
3. Your frontend renders the `iframe_url` in an iframe.
4. The user completes phone verification, the form, and Meta Embedded Signup inside the iframe.
5. On completion, ChatArchitect sends a signed webhook to your backend.
6. Your backend stores the returned `app_id` and `app_secret`.
7. If webhook delivery fails or you need a fallback, your backend can call the result endpoint.

The iframe never exposes `app_secret` to the browser, the DOM, URLs, or `postMessage`. Final credentials are delivered only to your backend through the webhook or the Partner Result API.

## Base URL

Use the public base URL provided by ChatArchitect for your environment:

```text
https://<chatarchitect-registration-domain>
```

All Partner API endpoints in this guide are relative to that base URL.

## Authentication

All Partner API requests require a Bearer token:

```http
Authorization: Bearer <partner_token>
```

ChatArchitect provides this token to you. Treat it as a secret. The same value is also used as the HMAC secret for webhook signature verification.

## Create Embed Session

Create a new registration session from your backend.

```http
POST /partner?action=create_session
Authorization: Bearer <partner_token>
Content-Type: application/json
```

Request body:

```json
{
  "parent_origin": "https://partner.example",
  "webhook_url": "https://partner.example/api/chatarchitect/whatsapp-registration-webhook",
  "partner_user_id": "user_456",
  "partner_state": "opaque-state-value",
  "integration": "My CRM",
  "lang": "en"
}
```

Fields:

| Field | Required | Description |
| --- | --- | --- |
| `parent_origin` | Yes | Origin of the parent page that will host the iframe, for example `https://partner.example`. Use scheme, host, and optional port only. Must start with `http://` or `https://`. Used as the target origin for iframe `postMessage` events. |
| `webhook_url` | Yes | HTTPS URL that receives the completion webhook. Must start with `https://`. |
| `partner_user_id` | No | Your user/account/customer id. Returned unchanged in the webhook. |
| `partner_state` | No | Opaque value for your own correlation or CSRF/session checks. Returned unchanged in the webhook. |
| `integration` | No | Optional custom integration name. It can be any string. If omitted or blank, the registration uses `Partner`. |
| `lang` | No | Initial language code. Defaults to `en`. Supported values: `de`, `en`, `es`, `pt`, `ru`. |

Successful response:

```json
{
  "registration_id": "abc123def456ghi789jkl0",
  "iframe_url": "https://<chatarchitect-registration-domain>/?s=abc123def456ghi789jkl0&mode=embed",
  "expires_at": "2026-07-05T10:00:00+00:00",
  "status": "started"
}
```

Notes:

- Store `registration_id` in your backend. You need it for result polling and webhook retry.
- `iframe_url` is safe to send to your frontend.
- The stored session mode is authoritative. Adding `mode=embed` to another URL does not convert a standalone session into an embed session.

Example with `curl`:

```bash
curl -X POST "https://<chatarchitect-registration-domain>/partner?action=create_session" \
  -H "Authorization: Bearer <partner_token>" \
  -H "Content-Type: application/json" \
  -d '{
    "parent_origin": "https://partner.example",
    "webhook_url": "https://partner.example/api/chatarchitect/whatsapp-registration-webhook",
    "partner_user_id": "user_456",
    "partner_state": "opaque-state-value",
    "integration": "My CRM",
    "lang": "en"
  }'
```

## Embed the Iframe

Render the returned `iframe_url` in your frontend.

```html
<iframe
  id="ca-whatsapp-registration"
  src="https://<chatarchitect-registration-domain>/?s=abc123def456ghi789jkl0&mode=embed"
  style="width: 100%; height: 720px; border: 0;"
  allow="clipboard-write; encrypted-media; fullscreen"
></iframe>
```

Avoid a fixed `min-height` if the iframe sits inside a modal or another constrained container. The iframe sends `height` events as its content changes, so the parent page can shrink or grow the iframe instead of forcing an extra scrollbar.

If you use the `sandbox` attribute, include enough permissions for the registration flow and Meta Embedded Signup:

```html
<iframe
  src="https://<chatarchitect-registration-domain>/?s=abc123def456ghi789jkl0&mode=embed"
  sandbox="allow-scripts allow-forms allow-same-origin allow-popups allow-popups-to-escape-sandbox"
></iframe>
```

Avoid placing secrets in the iframe URL. The only required iframe state is already contained in `iframe_url`.

## Iframe Events

The iframe sends status-only events to the parent window using `postMessage`.

Message shape:

```json
{
  "type": "ca.whatsapp.registration",
  "event": "ready",
  "registration_id": "abc123def456ghi789jkl0"
}
```

Events:

| Event | Meaning |
| --- | --- |
| `ready` | Iframe loaded and initialized. |
| `height` | Iframe height changed. Payload can include `height`. |
| `phone_verified` | User verified the WhatsApp phone number. |
| `facebook_started` | User clicked Connect WhatsApp and Meta Embedded Signup started. |
| `completed` | Registration completed. Fetch credentials from your backend storage or Partner Result API. |
| `failed` | Registration failed. Payload can include `message`. |
| `cancelled` | User cancelled Meta Embedded Signup. |

Parent-page listener example:

```html
<script>
  const iframe = document.getElementById('ca-whatsapp-registration');
  const expectedOrigin = 'https://<chatarchitect-registration-domain>';
  const minHeight = 480;
  const maxModalHeight = () => Math.max(minHeight, window.innerHeight - 220);

  window.addEventListener('message', (event) => {
    if (event.origin !== expectedOrigin) return;

    const data = event.data || {};
    if (data.type !== 'ca.whatsapp.registration') return;

    if (data.event === 'height' && data.height) {
      const contentHeight = Math.max(minHeight, Number(data.height));
      iframe.style.height = `${Math.min(contentHeight, maxModalHeight())}px`;
    }

    if (data.event === 'completed') {
      // Your backend should receive the signed webhook.
      // Optionally call your own backend to refresh registration status.
    }

    if (data.event === 'failed') {
      // Show a retry/support state in your UI.
    }
  });
</script>
```

`postMessage` never contains `app_secret`.

## Completion Webhook

When an embed registration completes, ChatArchitect sends a `POST` request to the `webhook_url` from the session creation request.

Payload:

```json
{
  "event": "whatsapp.registration.completed",
  "registration_id": "abc123def456ghi789jkl0",
  "partner_user_id": "user_456",
  "partner_state": "opaque-state-value",
  "app_id": "ca_app_123",
  "app_secret": "secret_value",
  "phone": "421233221242",
  "integration": "My CRM",
  "completed_at": "2026-07-04T10:00:00+00:00"
}
```

Headers:

```http
Content-Type: application/json
X-CA-Event-Id: evt_<registration_id>_<attempt>
X-CA-Timestamp: <unix_timestamp>
X-CA-Signature: sha256=<hmac_sha256>
```

Signature formula:

```text
sha256=<HMAC_SHA256(X-CA-Timestamp + "." + raw_request_body, partner_token)>
```

Verification requirements:

- Read the raw request body before JSON parsing.
- Reject stale timestamps according to your replay window, for example 5 minutes.
- Compute HMAC SHA-256 using your Partner API Bearer token as the secret.
- Compare the computed signature with `X-CA-Signature` using constant-time comparison.
- Process events idempotently by `X-CA-Event-Id` or `registration_id`.

Node.js verification example:

```js
import crypto from 'node:crypto';

function verifyChatArchitectWebhook({ rawBody, timestamp, signature, partnerToken }) {
  const expected = 'sha256=' + crypto
    .createHmac('sha256', partnerToken)
    .update(`${timestamp}.${rawBody}`)
    .digest('hex');

  const expectedBuffer = Buffer.from(expected);
  const signatureBuffer = Buffer.from(signature || '');
  if (expectedBuffer.length !== signatureBuffer.length) {
    return false;
  }

  return crypto.timingSafeEqual(
    expectedBuffer,
    signatureBuffer
  );
}
```

Respond with any `2xx` status when the webhook is accepted. Non-`2xx` responses are treated as failed delivery and can be retried through the Partner API.

## Get Registration Result

Use the result endpoint as a backend fallback or status check.

```http
GET /partner?action=result&registration_id=<registration_id>
Authorization: Bearer <partner_token>
```

If registration is not completed yet:

```json
{
  "status": "phone_verified"
}
```

Possible in-progress statuses include:

```text
started
phone_code_sent
phone_verified
form_completed
fb_started
expired
```

If registration is completed:

```json
{
  "status": "completed",
  "registration_id": "abc123def456ghi789jkl0",
  "app_id": "ca_app_123",
  "app_secret": "secret_value",
  "phone": "421233221242",
  "integration": "My CRM",
  "completed_at": "2026-07-04T10:00:00+00:00"
}
```

The result endpoint only works for the partner token that created the session.

## Retry Webhook

Retry webhook delivery after a completed registration.

```http
POST /partner?action=retry_webhook
Authorization: Bearer <partner_token>
Content-Type: application/json
```

Request body:

```json
{
  "registration_id": "abc123def456ghi789jkl0"
}
```

Response:

```json
{
  "status": "sent",
  "attempts": 2,
  "last_error": ""
}
```

If the registration is not completed, the endpoint returns `409`.

## Error Responses

Common error shape:

```json
{
  "status": "error",
  "message": "Invalid registration id"
}
```

Common HTTP statuses:

| Status | Meaning |
| --- | --- |
| `400` | Invalid action, request body, `parent_origin`, `webhook_url`, or registration id. |
| `401` | Missing or invalid Bearer token. |
| `404` | Session not found, or it belongs to another partner. |
| `409` | Retry requested before registration completion. |
| `500` | Server-side failure. |

## Security Checklist

- Call Partner API only from your backend, never from browser JavaScript.
- Keep the Partner API token secret. It is also the webhook signing secret.
- Use HTTPS for your parent page and webhook endpoint in production.
- Verify every webhook signature before trusting `app_id` or `app_secret`.
- Store `app_secret` only in backend storage.
- Treat iframe `postMessage` events as status hints only, not as a source of credentials.
- Validate `event.origin` when receiving iframe messages.
- Handle duplicate webhooks idempotently.

## Recommended Flow

```mermaid
sequenceDiagram
    participant PartnerBackend as Partner backend
    participant PartnerFrontend as Partner frontend
    participant CA as ChatArchitect registration
    participant User as User

    PartnerBackend->>CA: POST /partner?action=create_session
    CA-->>PartnerBackend: registration_id, iframe_url
    PartnerBackend-->>PartnerFrontend: iframe_url
    PartnerFrontend->>CA: Load iframe_url
    CA-->>PartnerFrontend: postMessage ready/height
    User->>CA: Verify phone, fill form, complete Meta signup
    CA-->>PartnerFrontend: postMessage completed
    CA->>PartnerBackend: Signed completion webhook
    PartnerBackend-->>CA: 2xx accepted
    PartnerBackend->>PartnerBackend: Store app_id and app_secret
```

# WhatsApp API Quick Start for Developers

Use this guide to add ChatArchitect WhatsApp messaging to any third-party service. It covers credentials, outbound messages, templates, delivery statuses, inbound messages, and a basic test flow.

<div style="border: 1px solid #d7d7d7; background: #ffffff; margin: 0 0 24px 0; overflow: hidden;">
  <table style="width: 100%; border-collapse: collapse; margin: 0; background: #ffffff;">
    <tbody>
      <tr style="border-bottom: 1px solid #e5e5e0;">
        <td style="padding: 14px 24px; color: #555; font-size: 18px; line-height: 1.3;">Agent instruction</td>
        <td style="padding: 14px 24px; color: #555; font-size: 18px; line-height: 1.3; text-align: right;">Copy</td>
      </tr>
    </tbody>
  </table>
  <pre style="margin: 0; padding: 14px 18px; background: #ffffff; color: #00112b; font-family: Consolas, 'Liberation Mono', Menlo, monospace; font-size: 15px; line-height: 1.55; white-space: pre-wrap;"><code>Implement ChatArchitect WhatsApp API connection for this codebase using the official guide:
https://support.chatarchitect.com/books/whatsapp-for-partners/page/whatsapp-api-quick-start-for-developers
Inspect the app&rsquo;s existing architecture first, then add server-side credential handling, message sending,
template loading, webhook processing for statuses and inbound messages, and minimal UI/settings needed for users to connect.</code></pre>
</div>

## What you will build

By the end of this guide, your service can:

- Send WhatsApp text messages and approved template messages.
- Receive asynchronous delivery statuses through a webhook: `submitted`, then `failed`, `sent`, `delivered`, or `read`.
- Receive inbound customer messages through the same webhook.
- Fetch approved WhatsApp templates from the ChatArchitect API.

If a customer writes first, you can reply without a template inside the active customer service window. If your business starts the conversation, use an approved WhatsApp template.

## Prerequisites and credentials

The customer must connect WhatsApp Business API through ChatArchitect support and Meta. After setup, ChatArchitect provides:

| Credential | Purpose |
| --- | --- |
| `APP_ID` | Basic Auth username for ChatArchitect API requests. |
| `APP_SECRET` | Basic Auth password for ChatArchitect API requests. |

Store both values server-side. Do not expose `APP_SECRET` in browser code, public logs, client-side configuration, or URLs.

All examples below use:

```text
API base URL: https://api.chatarchitect.com
Auth: Basic Auth with <APP_ID>:<APP_SECRET>
Recipient phone: <recipient_phone>
Webhook URL: <webhook_url>
```

Use phone numbers in international format without `+`, spaces, or brackets, for example `421233221242`.

## Quick start: 3 API calls

### Step 1 - Register a webhook

Register an HTTPS webhook URL for delivery statuses and inbound messages.

```http
POST https://api.chatarchitect.com/webhook
Authorization: Basic base64(<APP_ID>:<APP_SECRET>)
Content-Type: application/json
```

Request body:

```json
{
  "channel": "whatsapp",
  "destination": "<recipient_phone>",
  "webhook_separate": "false",
  "webhook": "<webhook_url>"
}
```

`destination` can be any WhatsApp number connected to the account. `webhook` must be an HTTPS URL reachable from the public internet.

cURL example:

```bash
curl -X POST "https://api.chatarchitect.com/webhook" \
  -u "<APP_ID>:<APP_SECRET>" \
  -H "Content-Type: application/json" \
  -d '{
    "channel": "whatsapp",
    "destination": "<recipient_phone>",
    "webhook_separate": "false",
    "webhook": "<webhook_url>"
  }'
```

### Step 2 - Send a text message

Send an outbound WhatsApp message.

```http
POST https://api.chatarchitect.com/whatsappmessage
Authorization: Basic base64(<APP_ID>:<APP_SECRET>)
Content-Type: application/json
```

Request body:

```json
{
  "channel": "whatsapp",
  "destination": "<recipient_phone>",
  "payload": {
    "type": "text",
    "message": "Hi John, how are you?"
  }
}
```

The synchronous response confirms that the message was accepted into the queue:

```json
{
  "status": "submitted",
  "messageId": "21110c1d-53e1-42b5-9454-1de9a09d4777"
}
```

Final delivery status arrives later through your webhook.

cURL example:

```bash
curl -X POST "https://api.chatarchitect.com/whatsappmessage" \
  -u "<APP_ID>:<APP_SECRET>" \
  -H "Content-Type: application/json" \
  -d '{
    "channel": "whatsapp",
    "destination": "<recipient_phone>",
    "payload": {
      "type": "text",
      "message": "Hi John, how are you?"
    }
  }'
```

### Step 3 - Get approved templates

Fetch approved WhatsApp templates available for the connected account.

```http
POST https://api.chatarchitect.com/getHSM
Authorization: Basic base64(<APP_ID>:<APP_SECRET>)
Content-Type: application/json
```

Request body:

```json
{
  "channel": "whatsapp",
  "destination": "<recipient_phone>",
  "getHSM": "true"
}
```

Response fragment:

```json
{
  "status": "submitted",
  "templates_status": true,
  "templates": [
    {
      "appId": "abcf5776-29e0-4e3a-a8ed-09c51e69d53e",
      "category": "MARKETING",
      "data": "{{26815959-f5b8-49b4-9b0e-7458d34777cc}}\nHello{{1}}"
    }
  ]
}
```

`templates.data` contains the template send syntax. Store or cache this list in your service so users can select approved templates.

cURL example:

```bash
curl -X POST "https://api.chatarchitect.com/getHSM" \
  -u "<APP_ID>:<APP_SECRET>" \
  -H "Content-Type: application/json" \
  -d '{
    "channel": "whatsapp",
    "destination": "<recipient_phone>",
    "getHSM": "true"
  }'
```

## Template rules

### Template types

| Type | Use case |
| --- | --- |
| Text-only | Standard notifications, confirmations, reminders, and updates. |
| Media | Messages with text plus an image, video, or document. |

For most notifications, text-only templates are enough.

### Template categories

| Category | Typical use |
| --- | --- |
| `MARKETING` | Promotions, offers, reactivation, and other non-transactional messages. |
| `UTILITY` | Transactional or service messages without advertising content. |
| `OTP` | One-time passwords and verification codes. |

Template category affects approval and pricing. Keep promotional content out of `UTILITY` and `OTP` templates.

### Template approval

Customers can submit templates through the ChatArchitect application or through ChatArchitect support. If your service needs a deeper integration, you can add template submission through API as a separate feature.

### Template send syntax

A template returned by `/getHSM` can look like this:

```text
{{26815959-f5b8-49b4-9b0e-7458d34777cc}}\nHello{{1}}
```

Rules:

- The first `{{...}}` block is the required template ID.
- `{{1}}`, `{{2}}`, and other numbered placeholders are template variables.
- Variable values can contain spaces and multiple words.
- Variable values must not contain line breaks.

To send a template message, use the same `/whatsappmessage` endpoint. The difference from a normal text message is the value of `payload.message`.

Example template send body:

```json
{
  "channel": "whatsapp",
  "destination": "<recipient_phone>",
  "payload": {
    "type": "text",
    "message": "{{26815959-f5b8-49b4-9b0e-7458d34777cc}}\nHello John"
  }
}
```

## Webhook events

### Status events

Delivery errors are asynchronous. A send request can return `submitted`, while final failure details arrive later in the webhook.

Example status event:

```json
{
  "app": "aQWPjCAmjav",
  "timestamp": 1580311136040,
  "version": 2,
  "type": "message-event",
  "payload": {
    "id": "ee4a68a0-1203-4c85-8dc3-49d0b3226a35",
    "type": "failed",
    "destination": "<recipient_phone>",
    "payload": {
      "code": 1008,
      "reason": "User is not Opted in and Inactive"
    }
  }
}
```

Important fields:

| Field | Meaning |
| --- | --- |
| Root `type` | `message-event` for status updates. |
| `payload.type` | Final status: `failed`, `sent`, `delivered`, or `read`. |
| `payload.destination` | Customer phone number. |
| `payload.payload.reason` | Failure reason, present for failed messages when available. |

### Inbound message events

Example inbound text message:

```json
{
  "app": "aQWPjCAmjav",
  "timestamp": 1580227766370,
  "version": 2,
  "type": "message",
  "payload": {
    "id": "ABEGkYaYVSEEAhAL3SLAWwHKeKrt6s3FKB0c",
    "source": "<recipient_phone>",
    "type": "text",
    "payload": {
      "text": "Hi"
    }
  }
}
```

Important fields:

| Field | Meaning |
| --- | --- |
| Root `type` | `message` for inbound customer messages. |
| `payload.source` | Customer phone number. |
| `payload.type` | Inbound message type. |
| `payload.payload.text` | Text content for text messages. |

### Correlation

Store your outbound request metadata together with the synchronous `messageId`. For webhook processing, also use the customer phone number and webhook `payload.id` to match status events and inbound replies to your internal records.

## WhatsApp policy essentials

Follow these practical rules when designing messaging in your service:

- Send campaigns only to customers who expect messages from the business.
- Use approved templates when the business starts the conversation.
- Add an unsubscribe option to marketing templates when relevant.
- Avoid cold lists. High complaint rates can lead to template restrictions or temporary sending limits.
- Keep marketing, utility, and OTP content separated by template category.
- Monitor failed deliveries and complaints before increasing volume.

## What to implement in your service

A useful integration usually includes these parts:

| Feature | Recommended behavior |
| --- | --- |
| Credentials | Store `APP_ID` and `APP_SECRET` server-side per customer account. |
| Template list | Fetch templates from `/getHSM`, show category and text preview, and let users refresh the list. |
| Template variables | Detect `{{1}}`, `{{2}}`, and other placeholders and let users map them to service fields. |
| Audience selection | Let users select recipients and preview personalized messages before sending. |
| Send history | Store outbound request time, synchronous `messageId`, final webhook statuses, and failure reasons. |
| Inbound messages | Either route replies to another channel or show them in your own conversation UI. |
| Webhook processing | Accept status and inbound events on HTTPS, validate data shape, and process duplicate events safely. |

## Code examples

### cURL - send text

```bash
curl -X POST "https://api.chatarchitect.com/whatsappmessage" \
  -u "<APP_ID>:<APP_SECRET>" \
  -H "Content-Type: application/json" \
  -d '{
    "channel": "whatsapp",
    "destination": "<recipient_phone>",
    "payload": {
      "type": "text",
      "message": "Hi John, how are you?"
    }
  }'
```

### Node.js - send text with fetch

```js
const APP_ID = process.env.APP_ID;
const APP_SECRET = process.env.APP_SECRET;

const auth = Buffer.from(`${APP_ID}:${APP_SECRET}`).toString('base64');

async function sendText(destination, text) {
  const response = await fetch('https://api.chatarchitect.com/whatsappmessage', {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json',
      'Authorization': `Basic ${auth}`
    },
    body: JSON.stringify({
      channel: 'whatsapp',
      destination,
      payload: {
        type: 'text',
        message: text
      }
    })
  });

  if (!response.ok) {
    throw new Error(`ChatArchitect API error: ${response.status}`);
  }

  return response.json();
}

sendText('<recipient_phone>', 'Hi John, how are you?')
  .then(console.log)
  .catch(console.error);
```

### Python - send text with requests

```python
import os
import requests

APP_ID = os.environ["APP_ID"]
APP_SECRET = os.environ["APP_SECRET"]

response = requests.post(
    "https://api.chatarchitect.com/whatsappmessage",
    auth=(APP_ID, APP_SECRET),
    headers={"Content-Type": "application/json"},
    json={
        "channel": "whatsapp",
        "destination": "<recipient_phone>",
        "payload": {
            "type": "text",
            "message": "Hi John, how are you?",
        },
    },
    timeout=30,
)
response.raise_for_status()
print(response.json())
```

## Test checklist

Use this checklist before showing the integration to customers:

- Register an HTTPS webhook with `/webhook`.
- Send a normal text message with `/whatsappmessage`.
- Confirm the synchronous response contains `status: submitted`.
- Receive a final `message-event` webhook with `failed`, `sent`, `delivered`, or `read`.
- Fetch templates with `/getHSM`.
- Send one approved template message with a real variable value.
- Confirm failed template sends display the webhook failure reason in your service.
- Send an inbound WhatsApp message from the customer phone and confirm your webhook receives root `type: message`.

## Customer message handling options

| Option | Behavior | Best for |
| --- | --- | --- |
| Minimal | Ignore inbound messages in your service and route operational replies to another channel such as a help desk, email inbox, or team chat. | Fast launch, notification-only use cases. |
| Advanced | Show inbound messages in your service and let users reply without templates when the customer wrote first. | Full conversation workflows. |

You can start with the minimal option and add a conversation UI later.

## Partner-facing integration description example

Use a short description like this when presenting the feature to your customers:

```text
ChatArchitect.com WhatsApp for <Your Service>

Send and receive WhatsApp messages directly from <Your Service>. Start conversations with approved WhatsApp templates, receive customer replies, and track delivery statuses automatically through webhooks.
```

Recommended feature bullets:

- Incoming and outgoing WhatsApp messages.
- Business-initiated messages with approved templates.
- Delivery status tracking: sent, delivered, read, and failed.
- Template preview and variable mapping.
- Optional bulk messaging from your own audience filters.

## Recommended flow

```mermaid
sequenceDiagram
    participant Service as Your service
    participant CA as ChatArchitect API
    participant WA as WhatsApp user

    Service->>CA: POST /webhook
    CA-->>Service: Webhook saved
    Service->>CA: POST /getHSM
    CA-->>Service: Approved templates
    Service->>CA: POST /whatsappmessage
    CA-->>Service: status=submitted, messageId
    CA->>Service: message-event sent/delivered/read/failed
    WA->>CA: Customer reply
    CA->>Service: inbound message webhook
```

# New user_id, parent_user_id, and username fields in the ChatArchitect API

ChatArchitect API provides additional WhatsApp user identifiers: `user_id`, `parent_user_id`, and `username`. They make it possible to work with users who interact with a business through a username and may not disclose their real phone number.

At the same time, ChatArchitect remains fully compatible with existing CRMs, help desks, chatbots, and other integrations that expect every WhatsApp user to have a phone number.

If a user's real phone number is unavailable, ChatArchitect automatically creates a synthetic number for that user. The phone number field therefore remains present:

- In inbound messages.
- In status webhooks.
- In other related webhooks.
- When sending outbound messages.

An integration can continue using the phone number as the primary contact identifier without changing its existing logic.

## Summary

Inbound messages use these fields:

```text
payload.sender.phone
payload.sender.user_id
payload.sender.parent_user_id
payload.sender.username
```

Status webhooks use:

```text
payload.destination
payload.userId
payload.parentUserId
payload.profileUsername
```

To send a message, use either:

```text
destination
```

or:

```text
userId
```

`destination` accepts either a real phone number or a synthetic number created by ChatArchitect.

`userId` accepts either a BSUID or a Parent BSUID.

## 1. What `user_id` means

`user_id` is a Business-Scoped User ID, abbreviated as BSUID.

A BSUID uniquely identifies a WhatsApp user within a specific business. It is created for each business-user combination and makes it possible to interact with the user even when their real phone number is unavailable.

A BSUID is created regardless of whether the user has set a WhatsApp username.

Example BSUIDs:

```text
US.13491208655302741918
IN.34661116580198991
```

The identifier starts with a two-letter ISO country code, followed by a period and a unique sequence of characters.

General format:

```text
<ISO country code>.<unique identifier>
```

Example:

```text
SK.34661116580198991
```

Store the BSUID exactly as received from ChatArchitect API.

Do not:

- Change the letter case.
- Remove the country code.
- Remove the period.
- Convert the value to a number.
- Shorten the identifier.
- Generate a BSUID yourself.

A BSUID may change, for example, if the user changes the phone number associated with WhatsApp. In this case, process the change and update the stored user data.

## 2. Where to find `user_id` in an inbound webhook

In an inbound message, the BSUID is located at:

```text
payload.sender.user_id
```

Example:

```json
{
  "version": 2,
  "type": "message",
  "payload": {
    "sender": {
      "phone": "4210012345678",
      "user_id": "SK.34661116580198991",
      "parent_user_id": "",
      "name": "Customer name",
      "username": "customer_username"
    }
  }
}
```

The `sender` object contains the phone number and additional user identifiers:

```text
phone
user_id
parent_user_id
username
```

The `phone` field is always present.

If the user's real number is available, `phone` contains the real number.

If the real number is unavailable, ChatArchitect creates a synthetic number and returns it in the same `phone` field.

This allows legacy integrations to continue working without mandatory BSUID support.

## 3. ChatArchitect synthetic numbers

If a user interacts with a business through a WhatsApp username and does not disclose their real phone number, ChatArchitect automatically creates a synthetic number for that user.

General format:

```text
+<country code>00<internal identifier>
```

The key indicator of a synthetic number is the two digits `00` immediately after the country code.

Example:

```text
+42100...
```

Here:

```text
+421
```

is the country code, and:

```text
00
```

indicates that the number was created by ChatArchitect and is not the user's real phone number.

### How to identify a synthetic number

First determine the country code, then inspect the first digits of the national part of the number.

If the first two digits after the country code are `00`, the number is a ChatArchitect synthetic number.

Pattern:

```text
+<country_code>00<identifier>
```

Parse the international country code correctly before checking the number. Do not assume the country code always contains one, two, or three digits without first parsing it.

Recommended logic:

```text
1. Determine the international country code.
2. Separate the country code from the national part.
3. Check whether the national part starts with 00.
4. If it does, the number is a ChatArchitect synthetic number.
```

A synthetic number:

- Is not the user's real phone number.
- Is not intended for regular phone calls.
- Must not be used for SMS.
- Is used within ChatArchitect and connected integrations.
- Preserves legacy data models in which a phone number is a required identifier.
- Can be used to send messages through ChatArchitect API.

## 4. Sending a message to a synthetic number

To send an outbound message, use the standard parameter:

```text
destination
```

It accepts:

- A real WhatsApp phone number.
- A ChatArchitect synthetic number.

Example:

```text
destination=4210012345678
```

The integration does not need to convert a synthetic number to a BSUID. ChatArchitect resolves the associated user and sends the message to the correct WhatsApp recipient.

This lets legacy integrations continue using the same flow:

```text
contact → phone number → send message
```

Even if WhatsApp does not provide the user's real number, the integration still receives a `phone` value that can be stored in a CRM and used as `destination`.

## 5. `user_id` in a billing-event webhook

In a billing webhook, the BSUID is located at:

```text
payload.references.user_id
```

Example:

```json
{
  "version": 2,
  "type": "billing-event",
  "payload": {
    "references": {
      "destination": "4210012345678",
      "user_id": "SK.1009956278697109"
    }
  }
}
```

In this webhook:

```text
destination
```

contains the number used by the integration. It can be either a real or synthetic number.

The field:

```text
user_id
```

contains the WhatsApp user's BSUID.

Note that billing events use:

```text
user_id
```

not:

```text
userId
```

## 6. What `parent_user_id` means

`parent_user_id` is a Parent BSUID.

A regular BSUID identifies a user within a specific business or business portfolio. A Parent BSUID is intended for large organizations, managed businesses, and structures that use multiple connected Business Managers or business portfolios.

A Parent BSUID makes it possible to identify the same end user across different business portfolios owned by one large organization.

In an inbound message, the field is located at:

```text
payload.sender.parent_user_id
```

Example:

```json
{
  "payload": {
    "sender": {
      "phone": "4210012345678",
      "user_id": "SK.34661116580198991",
      "parent_user_id": "SK.PARENT_IDENTIFIER",
      "username": "customer_username"
    }
  }
}
```

`parent_user_id` may be empty when a Parent BSUID is not used or is unavailable for the relevant business structure.

The integration must therefore treat it as optional.

Correct logic:

```text
phone — always present;
user_id — the user's primary BSUID;
parent_user_id — an additional identifier for connected business portfolios;
username — the user's current WhatsApp username, when available.
```

## 7. What `username` means

`username` is the username selected by the end user in WhatsApp.

A username is optional. A user can:

- Set a username.
- Change it.
- Remove it.
- Use it to interact without disclosing their real phone number.

A username change does not mean the user has changed.

In particular, changing a username:

- Does not necessarily change the BSUID.
- Must not create a new contact in the CRM.
- Must not be treated as the only persistent identifier.
- Should update the displayed username of the existing contact.

In an inbound webhook, the username is located at:

```text
payload.sender.username
```

Example:

```json
{
  "payload": {
    "sender": {
      "phone": "4210012345678",
      "user_id": "SK.34661116580198991",
      "parent_user_id": "",
      "name": "Customer name",
      "username": "customer_username"
    }
  }
}
```

The username can be used:

- As a display name for an agent.
- To search for a contact.
- To personalize the interface.
- As an additional CRM field.
- In message history.
- To keep the user profile current.

Do not use the username as a primary key because the user can change it.

For persistent identification, use:

```text
user_id
```

or the number from:

```text
phone
```

In ChatArchitect, `phone` is always available, even when the user's real number is hidden.

## 8. Inbound webhook when the real number is hidden

When the user's real number is unavailable, ChatArchitect does not remove the `phone` field.

Instead, it contains a synthetic number:

```json
{
  "version": 2,
  "type": "message",
  "payload": {
    "sender": {
      "phone": "4210012345678",
      "user_id": "SK.34661116580198991",
      "parent_user_id": "",
      "name": "",
      "username": "customer_username",
      "country_code": "421",
      "dial_code": "+421"
    }
  }
}
```

In this example:

```text
4210012345678
```

is synthetic because the country code `421` is immediately followed by `00`.

The integration can process the event like a regular message from a user with a real number:

1. Find the contact by `phone`.
2. If the contact does not exist, create it.
3. Store `user_id`.
4. Store `parent_user_id` when provided.
5. Store or update `username`.
6. Add the inbound message to the history.
7. Use `phone` for subsequent outbound messages.

This keeps the required phone field available and prevents legacy integrations from breaking.

## 9. Fields in a status webhook

Outbound message status webhooks use these fields:

```text
payload.destination
payload.userId
payload.parentUserId
payload.profileUsername
```

Field names differ from those in an inbound webhook.

| Value | Inbound message | Status webhook |
| --- | --- | --- |
| Number | `sender.phone` | `destination` |
| BSUID | `sender.user_id` | `userId` |
| Parent BSUID | `sender.parent_user_id` | `parentUserId` |
| Username | `sender.username` | `profileUsername` |

Example:

```json
{
  "version": 2,
  "type": "message-event",
  "payload": {
    "type": "delivered",
    "destination": "4210012345678",
    "userId": "SK.34661116580198991",
    "parentUserId": "",
    "profileUsername": "customer_username"
  }
}
```

The `destination` field is always present.

It contains:

- The real number, when available.
- A ChatArchitect synthetic number, when the real number is hidden.

Status webhooks can therefore be linked to outbound messages through the familiar `destination` field.

You can also use `userId` to associate the event more precisely with the WhatsApp user.

## 10. Sending a message through `userId`

In addition to sending by phone number, ChatArchitect API can send messages by BSUID.

Use this parameter:

```text
userId
```

It accepts:

- A BSUID.
- A Parent BSUID.

Example:

```text
userId=SK.35263896023254374
```

This sends a message directly to the user's business-scoped identifier.

For most existing integrations, however, it is sufficient to continue using:

```text
destination
```

Because ChatArchitect provides a synthetic number, integrations are not required to switch to `userId`.

## 11. Sending both `destination` and `userId`

A single request can include both:

```text
destination=<number>
userId=<BSUID or Parent BSUID>
```

When both parameters are present, this field takes priority:

```text
destination
```

In other words, the number passed in `destination` is treated as the primary recipient.

This rule also applies to ChatArchitect synthetic numbers.

Example:

```text
destination=4210012345678
userId=SK.34661116580198991
```

For a simpler integration, choose one primary addressing method:

- Use `destination` if the system is built around phone numbers.
- Use `userId` if the system already supports BSUID as its primary identifier.

## 12. BSUID validation

A BSUID is validated when a message is sent.

The API may immediately return an error if the supplied value is:

- An unknown BSUID.
- A malformed BSUID.
- A BSUID that does not belong to the business.
- A corrupted or truncated value.

Store BSUIDs without transformation.

For example, do not store only the numeric part:

```text
34661116580198991
```

Store the complete value:

```text
SK.34661116580198991
```

Unlike a BSUID, a synthetic number is used through the standard `destination` parameter, and ChatArchitect resolves the number to the user.

## 13. Limitations of sending by BSUID

Not all WhatsApp message types necessarily support sending by BSUID.

For an unsupported message type, the API may return this error:

```text
131062
Business-scoped User ID recipients are not supported for this message
```

In particular, authentication templates require a real phone number and do not support sending only by BSUID.

A ChatArchitect synthetic number does not turn a hidden user number into a real phone number. It is an internal addressing identifier and cannot be used where Meta requires the end user's real phone number.

For authentication templates, obtain the user's real phone number first.

## 14. Request Contact Info and obtaining the real number

If a user voluntarily shares their number through the Request Contact Info feature, the webhook contains both the user identifiers and the shared contact.

The sender object still contains:

```text
payload.sender.phone
payload.sender.user_id
payload.sender.parent_user_id
payload.sender.username
```

The real number shared by the user is inside the contact payload:

```text
payload.payload.contacts[].phones[].phone
payload.payload.contacts[].phones[].wa_id
```

Example:

```json
{
  "payload": {
    "sender": {
      "phone": "4210012345678",
      "user_id": "SK.34661116580198991",
      "parent_user_id": "",
      "username": "customer_username",
      "country_code": "421",
      "dial_code": "+421"
    },
    "payload": {
      "contacts": [
        {
          "phones": [
            {
              "phone": "+421900123456",
              "wa_id": "421900123456"
            }
          ]
        }
      ]
    }
  }
}
```

In this scenario:

```text
sender.phone
```

may contain the ChatArchitect synthetic number created earlier, while:

```text
contacts[].phones[].phone
```

contains the real number voluntarily shared by the user.

The integration can link:

- The synthetic number.
- The BSUID.
- The username.
- The real number.

This allows the contact record to be updated without creating a duplicate user.

## 15. How to store the new fields in a CRM or database

Recommended contact structure:

```text
phone
is_synthetic_phone
user_id
parent_user_id
username
real_phone
```

Where:

```text
phone
```

is the primary number used by the integration. It is always present and can be real or synthetic.

```text
is_synthetic_phone
```

indicates that the number starts with `00` immediately after the country code.

```text
user_id
```

is the user's BSUID.

```text
parent_user_id
```

is the Parent BSUID, when available.

```text
username
```

is the current WhatsApp username.

```text
real_phone
```

is the user's real number, if it was initially available or voluntarily shared later.

Example:

```json
{
  "phone": "4210012345678",
  "is_synthetic_phone": true,
  "user_id": "SK.34661116580198991",
  "parent_user_id": null,
  "username": "customer_username",
  "real_phone": null
}
```

After obtaining the real number:

```json
{
  "phone": "4210012345678",
  "is_synthetic_phone": true,
  "user_id": "SK.34661116580198991",
  "parent_user_id": null,
  "username": "customer_username",
  "real_phone": "421900123456"
}
```

The exact storage model can vary, but do not create a new user only because a username appears or changes.

## 16. Recommended user identification order

For new integrations, use this order:

1. Look up the user by `user_id`.
2. If the user is not found, look them up by `phone`.
3. Also consider `parent_user_id`.
4. Do not use `username` as the only persistent identifier.
5. When a real number becomes available, link it to the existing BSUID and synthetic number.

Legacy integrations can keep their current logic:

1. Look up the user by `phone`.
2. Create a contact by `phone` if it does not exist.
3. Send messages through `destination`.
4. Gradually add storage for `user_id`, `parent_user_id`, and `username`.

Synthetic numbers make this upgrade possible without urgently redesigning the existing integration.

## 17. Field summary

| Field | Location | Purpose |
| --- | --- | --- |
| `phone` | `payload.sender.phone` | Real or synthetic number; always present |
| `destination` | `payload.destination` | Number in a status webhook and parameter for sending |
| `user_id` | `payload.sender.user_id` | BSUID in an inbound message |
| `userId` | `payload.userId` or API parameter | BSUID in a status webhook and outbound request |
| `parent_user_id` | `payload.sender.parent_user_id` | Parent BSUID in an inbound message |
| `parentUserId` | `payload.parentUserId` | Parent BSUID in a status webhook |
| `username` | `payload.sender.username` | WhatsApp username in an inbound message |
| `profileUsername` | `payload.profileUsername` | WhatsApp username in a status webhook |
| `references.user_id` | `payload.references.user_id` | BSUID in a billing event |

## Key point for existing integrations

ChatArchitect preserves the existing model in which a phone number is required.

Even when WhatsApp does not provide the user's real phone number:

- `sender.phone` is still present.
- `destination` is present in status webhooks.
- Messages can be sent through `destination`.
- Contacts can be stored in a CRM by number.
- A synthetic number can be identified by `00` immediately after the country code.
- BSUID, Parent BSUID, and username are also available.

This supports the new WhatsApp username and BSUID features without breaking compatibility with legacy CRMs, help desks, chatbots, and custom integrations.