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

# WeChat onboarding: Official Account to template send

> Walk the WeChat channel end to end — provision a WeChat Official Account and access token, pick and approve a template, send through POST /api/v1/messages/wechat, target openids and metadata.url deep-links, and verify inbound webhook signatures.

# WeChat Onboarding: Official Account to Template Send

This guide walks you from an empty WeChat channel to a working, production-ready setup. It complements the [WeChat channel page](/channels/wechat), which covers every field, error code, and rate limit in reference form — this guide is the ordered path you follow the first time, and re-run whenever you connect, rotate, or retire an Official Account.

<Note>
  WeChat is a **beta** channel. The send and receive paths run end to end, but onboarding is bring-your-own-credential: nothing sends until you provision a WeChat Official Account, get at least one template approved, and connect the account's access token to Orbit.
</Note>

## 1. Prerequisites: Official Account and access token

WeChat Official Account messaging is template-only and follower-only. Two upstream prerequisites exist before Orbit can send anything:

1. **Register a WeChat Official Account** in your organization. Any account type (service or subscription) can send template messages if it is verified by WeChat — unverified accounts cannot select template message functions.
2. **Generate an access token** through the WeChat token grant. The token is short-lived (about two hours); WeChat returns `errcode 40001` the moment it expires. Because the token rotates this quickly, plan for a central refresh — see [Credential rotation](#credential-rotation) below.

Then connect the credential to Orbit:

3. In the Orbit dashboard, open **Settings → Channels → WeChat** and paste the access token. Orbit stores it encrypted at rest and masks it after submit; it never appears in an API response.

Until a credential is connected, sends fail closed with `503 CHANNEL_NOT_CONFIGURED`. That connection step is the only Orbit-side gate — there is no review cycle on the Orbit side, only WeChat's own account verification and template approval.

## 2. Pick and approve a template

WeChat Official Account messaging carries no free-form text: every send is a pre-approved template message selected by `template_name`, with `template_params` filling the template's placeholders.

* In the WeChat Official Account admin console, choose a template from the library (or submit your own) and wait for WeChat's approval. Each approved template gets a template id — that id is what you pass as `template_name`.
* Each template declares named placeholders in the `{{key.DATA}}` shape: `{{order_id.DATA}}`, `{{carrier.DATA}}`. Your `template_params` object is a flat string-to-string map keyed by those placeholder names, without the `{{...}}` plumbing — `{ "order_id": "A1024", "carrier": "SF Express" }`.

A send without an approved `template_name` is rejected with `422 VALIDATION_ERROR`, and an id the account has not been approved for comes back from WeChat as `errcode 40037` (`502 MESSAGE_SEND_FAILED` with the errcode in the error message). Pick the template upstream first; the API cannot substitute one.

## 3. Send your first template message

Send with `POST /api/v1/messages/wechat`:

<Tabs>
  <Tab title="curl">
    ```bash theme={null}
    curl -X POST https://api.orbit.devotel.io/api/v1/messages/wechat \
      -H "X-API-Key: dv_live_sk_..." \
      -H "Content-Type: application/json" \
      -d '{
        "to": "oABCdef1234567890ghijklmno",
        "template_name": "ORDER_SHIPPED_V2",
        "template_params": { "order_id": "A1024", "carrier": "SF Express" },
        "metadata": { "url": "https://h5.example.com/orders/A1024" }
      }'
    ```
  </Tab>

  <Tab title="Node.js">
    ```javascript theme={null}
    import { Orbit } from "@devotel-orbit/node";

    const orbit = new Orbit({ apiKey: process.env.ORBIT_API_KEY });

    const message = await orbit.request("POST", "/messages/wechat", {
      to: "oABCdef1234567890ghijklmno",
      template_name: "ORDER_SHIPPED_V2",
      template_params: { order_id: "A1024", carrier: "SF Express" },
      metadata: { url: "https://h5.example.com/orders/A1024" },
    });

    console.log(message.data.status); // 'queued'
    ```
  </Tab>

  <Tab title="Python">
    ```python theme={null}
    from orbit_sdk import OrbitClient

    client = OrbitClient.from_env()  # reads ORBIT_API_KEY

    message = client.request(
        "POST",
        "/messages/wechat",
        json_body={
            "to": "oABCdef1234567890ghijklmno",
            "template_name": "ORDER_SHIPPED_V2",
            "template_params": {"order_id": "A1024", "carrier": "SF Express"},
            "metadata": {"url": "https://h5.example.com/orders/A1024"},
        },
    )

    print(message["data"]["status"])  # 'queued'
    ```
  </Tab>
</Tabs>

You send the follower, the template, and its parameter values — never an access token. The sender is selected automatically from the Official Account credential you connected. `202 Accepted` means persisted and queued; the terminal `delivered` / `failed` state arrives later on the delivery-status webhook. To defer the send, pass `scheduled_at` as an RFC 3339 timestamp; the message sits as `scheduled` and stays editable or cancellable until dispatch (the pattern lives in [Message scheduling](/guides/message-scheduling)).

For a bulk broadcast, do not loop the single-send endpoint — submit a [campaign](/api-reference/endpoints/campaigns) with `channel: "wechat"`; the campaign carries the same `template_name` / `template_params` payload and skips recipients without a connected openid.

## 4. OpenID targeting and `metadata.url` deep links

Two WeChat-specific addressing details:

* **`to` is the follower's openid.** The openid is scoped to your Official Account — it is not the user's WeChat id, and it is not portable to another OA. Followers hand it to you when they contact you: every inbound reply carries the sender's openid on the `message.received` webhook (`event.data.from`), so store it on the contact record and reuse it for outbound targeting. A send to a string that is not a follower of your OA comes back from WeChat as `errcode 40003` — do not retry those; the recipient is not addressable.
* **`metadata.url` deep-links the message.** Set it to an H5 page and the follower taps the rendered template message straight into it — order details, a booking page, a claim form. `metadata` is echoed back on the delivery and inbound webhooks, so you can reconcile your own identifiers end to end.

## 5. Inbound: replies and signature verification (optional)

There is no WeChat webhook for you to register — the Orbit messaging gateway normalizes inbound replies and each `TEMPLATESENDJOBFINISH` delivery callback, then forwards them to you on the standard Orbit webhook surface. Subscribe under **Settings → Webhooks**:

* `message.received` with `channel: "wechat"` — a follower reply. The sender's openid sits under `event.data.from`.
* `message.status` with `channel: "wechat"` — the delivery callback advancing your outbound message to its terminal `delivered` / `failed` state.

Verify the signature on every inbound event before processing it — the Node and Python SDK helpers verify before your handler runs, and worked examples live in [Verify webhook signatures](/guides/verify-webhook-signatures). On the ingress side, the gateway forwards are already authenticated with a per-tenant binding and timestamp freshness, and spoofed or replayed forwards are rejected before they ever reach your endpoint.

## 6. Credential rotation and errors you will actually see

The access-token lifecycle is the one operational burden the beta channel leaves to you. The OA access token is a bearer credential — anyone holding it can send as your Official Account — so:

* **Rotate centrally and before expiry.** Refresh the token through the WeChat grant, then paste the new value under **Settings → Channels → WeChat**. While an expired token sits connected, sends fail with `errcode 40001` until the fresh one lands.
* **Never commit a token to git**, and scope each environment to its own Official Account so a leaked dev token cannot reach customer traffic.

| Code                     | HTTP | Cause                                                                                                             | Fix                                                                                                  |
| ------------------------ | ---- | ----------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------- |
| `VALIDATION_ERROR`       | 422  | No approved `template_name` on a send — WeChat OA messaging is template-only.                                     | Pick a template upstream and pass its id.                                                            |
| `INVALID_RECIPIENT`      | 422  | `to` is missing or empty.                                                                                         | Send the follower's openid.                                                                          |
| `CHANNEL_NOT_CONFIGURED` | 503  | No WeChat credential connected for your organization.                                                             | Paste the access token under **Settings → Channels → WeChat**.                                       |
| `MESSAGE_SEND_FAILED`    | 502  | WeChat rejected the send — errcode 40001 (token expired), 40003 (openid not a follower), 40037 (bad template id). | Read the errcode in the error message; rotate the token, drop the recipient, or fix the template id. |
| `RATE_LIMITED`           | 429  | More than 80 sends/minute on your organization.                                                                   | Honour `Retry-After`; stage bulk sends through campaigns.                                            |

Branch on the errcode rather than retrying blindly: 40001 and 40037 are recoverable, 40003 is not — the recipient cannot be reached.

## Compliance notes

Consent and opt-out remain tenant-owned controls. A follower reply of "unsubscribe" (or your locale-equivalent) arrives as a normal `message.received` inbound event — fold it into your suppression list just as you would for SMS, using the patterns in [Opt-out lists](/guides/opt-out-lists) and the [Public consent form](/guides/public-consent-form) guide for capture. WeChat's own template-approval process is upstream and is separate from Orbit's consent enforcement.

## Where to go next

* [WeChat channel page](/channels/wechat) — full field reference, scheduling semantics, campaign broadcasting, and the pricing note.
* [APAC channels onboarding](/guides/asia-channels-onboarding) — the same playbook for LINE, KakaoTalk, and Zalo, with a channel-picker per region.
* [Fallback chains](/guides/fallback-chains) — position WeChat as a last-hop fallback to SMS or vice versa.
* [Inbox setup](/guides/inbox-setup) — route inbound WeChat replies to your team.
