Add a WhatsApp account

Connects a WhatsApp Business Account shared with us through a Multi-Partner Solution to the calling sender profile. Send x-profile-id for the profile the account belongs to.

Two fields, because everything else is derived. waba_id says which of your organization's shared accounts this is — you hold one per client, and nothing else can say which. phone_number_id says which number on it to send from — an account may hold several, and choosing for you would mean deciding which of your customer's numbers their traffic leaves from.

The solution the account arrived through, the client business portfolio that owns it, the access token and its expiry are all resolved from your organization. They are not accepted here: the first two are Meta's answer about provenance, and the token is minted per client business and rotates roughly every sixty days, so a value you sent would be stale before it was useful.

phone_number_id is optional. Omit it to connect the account now and settle the number later — the channel reads as provisioning and the profile keeps sending from your organization's account until a number is named.

The same resolution runs behind channels.whatsapp on POST /v3/sender-profiles, so connecting at create and connecting afterwards fill the same fields.

Returns the account, not every channel you have — adding one cannot change a market or an RCS agent, and GET /v3/channels is the whole picture. Giving an account up is not self-service: ask support, because deregistering a number lands in your own Meta business.

POST
/v3/channels/whatsapp

Request Body

application/json

TypeScript Definitions

Use the request body type in TypeScript.

Response Body

application/json

application/json

application/json

curl -X POST "https://example.com/v3/channels/whatsapp" \  -H "Idempotency-Key: req_abc123_retry1" \  -H "Content-Type: application/json" \  -d '{    "waba_id": "102290129340398",    "phone_number_id": "106540352242922",    "sandbox": false  }'
{
  "success": true,
  "data": {
    "waba_id": "string",
    "phone_number_id": "string",
    "solution_id": "string",
    "owner_business_id": "string",
    "status": "PROVISIONING"
  },
  "error": {
    "code": "string",
    "message": "string",
    "details": {
      "property1": [
        "string"
      ],
      "property2": [
        "string"
      ]
    },
    "doc_url": "string"
  },
  "meta": {
    "request_id": "string",
    "timestamp": "2019-08-24T14:15:22Z",
    "version": "string"
  }
}
{
  "success": true,
  "error": {
    "code": "string",
    "message": "string",
    "details": {
      "property1": [
        "string"
      ],
      "property2": [
        "string"
      ]
    },
    "doc_url": "string"
  },
  "meta": {
    "request_id": "string",
    "timestamp": "2019-08-24T14:15:22Z",
    "version": "string"
  }
}
Empty
Empty
{
  "success": true,
  "error": {
    "code": "string",
    "message": "string",
    "details": {
      "property1": [
        "string"
      ],
      "property2": [
        "string"
      ]
    },
    "doc_url": "string"
  },
  "meta": {
    "request_id": "string",
    "timestamp": "2019-08-24T14:15:22Z",
    "version": "string"
  }
}

Amend one SMS market's compliance PATCH

Writes what this market declares for compliance, and answers with the market as stored — the same shape `GET` on this path returns, so **what comes back can be sent back**. ## Partial, at every level **An omitted key is left alone. An explicit `null` clears it.** Those are different requests, so `{}` changes nothing and is refused rather than silently accepted, and correcting one field does not blank the others. ```json { "compliance": { "brand": { "contact_phone": "+12125550123" } } } ``` That leaves the campaign untouched, along with every other field of the identity. ## What a US `TEN_DLC` market holds Two halves, both singular: `brand`, the identity the registration is filed against, and `campaign`, the programme filed against it. Each carries `inherit`. | Body | Effect | | --- | --- | | `"brand": { … }` | supplies or corrects the identity, and takes one of your own | | `"brand": { "inherit": true }` | runs on your organization's, giving up your own | | `"brand": null` | the same thing, said the short way | | `"campaign": { … }` | supplies or corrects the programme, and takes one of your own | | `"campaign": { "inherit": true }` | runs on your organization's | | key omitted | left exactly as it is | **Nothing here creates a second anything.** One identity and one campaign per market, so a write is an upsert and a replayed request lands on the row it already wrote. Sending the campaign twice does not file two registrations. ## Every other market Markets that register with no regime have neither half — their compliance is documents, which cannot be JSON values and are sent as multipart on `POST /v3/channels/sms`. Only US `TEN_DLC` registers, so this path is `404` everywhere else. ## What it will refuse **A filed identity is `409`.** Once the registration has gone to The Campaign Registry they hold a copy, and a local change would diverge from it silently. The campaign is not frozen — omit `brand` and the same body is accepted. **More than one campaign is `409`.** The schema permits several and an account built on the admin side may hold them; this surface writes one, and editing whichever sorted first would leave the others unmentioned and unedited. Nothing is submitted to a regime here. These are the rows a submission is later assembled from, which is what lets them be corrected without waiting on something externally rejectable. Send `x-profile-id` with an organization key to write one of your profiles' markets.

Add an RCS agent POST

Asks for an RCS agent and records the order form the carriers vet it against. Always `202`: an agent is approved by the carriers, not by this call, and nothing here can make that happen sooner. ## What it asks for The RCS Agent Order Form — the same one our team fills in on your behalf today, so nothing has to be collected from you twice. **Required, because US carriers refuse a submission without them:** - **`brand_name`** — the brand this agent presents. Not taken from `compliance.brand`: a business may present a different brand on RCS than the one its 10DLC registration is filed against, and the carriers vet this one. - **`privacy_policy_url`** and **`terms_and_conditions_url`** — fetched over the public internet at review time, so they must be absolute `http(s)` URLs. One behind a login passes this call and fails the review. **Everything else is optional here** and can follow. `display_name`, `description`, `agent_use_case` and `brand_color` are collected as the submission is assembled; `hosting_region` (`us` or `eu`) defaults to `us` and cannot be changed once approved; `billing_category` (`CONVERSATIONAL`, `SINGLE_MESSAGE`, `BASIC_MESSAGE`) defaults to `CONVERSATIONAL`. Contact details, `official_address`, the consent narrative and the start/help/stop messages are all part of the form. Anything supplied is checked against the accepted values rather than passed through: a value the provider rejects would otherwise surface days later, to somebody who cannot fix it without asking you. `logo_url` and `banner_url` are validated as URLs only. The form also specifies 224x224 and 50 KB for the logo and 1440x448 and 200 KB for the banner, which cannot be checked without the image bytes. **A customer has one agent.** Asking again while you have one is `409`. ## What comes back, and what does not Returns **the agent** — the form as stored, plus `id` and `status`. Not your other channels, which this call cannot have changed. `status` is `PROVISIONING` until the agent is handed to the carriers and approved. **`GET /v3/channels` reports `rcs: null` until then** — a channel row is what routing reads to decide you are sendable, and writing one for an unapproved agent would claim a channel that cannot carry traffic. Send `x-profile-id` with an organization key to ask for one of your profiles' agents.