人が送るのではないテンプレートもあります。タイドウォーター・ランドリーは、予約 システムが時間帯を確定した瞬間に集荷確定のメッセージを送りたいと考えています — そのために誰かが受信トレイを開く必要はないはずです。そのためのエンドポイントが 1 つあります。
POST https://api.omniocto.com/api/wa-templates/{id}/send
これは承認済みのテンプレートを 1 つの電話番号に送ります。webhook のハンドラーや スクリプトから呼び出してください — 「注文が発送されたら集荷テンプレートを送る」 のように。テンプレートなので、24 時間の窓は関係ありません (理由)。
API キーを取得する
キーは /settings/api-keys にあります — 歯車メニューの 管理とシステム → API キー です。
- キーを作成 をクリックします。ダイアログ API キーを作成 で 名前(任意) を求められます — 「例: CI パイプライン」 のように、使うシステムの名前を付けて ください。
- API キーを作成しました にキーが一度だけ表示され、「このキーは今すぐコピー してください。二度と表示できません。」 と添えられます。システムがシークレットを 保管する場所に保存してください。
一覧には各キーの 最終使用 日が表示され、キーを無効化 で即座に停止できます: 「これを使用している連携はすべて失敗します。」 連携ごとにキーを 1 つ使えば、これを 安全に行えます。
キーはすべての呼び出しで Authorization: Bearer <key> として送ります。
テンプレート ID を調べる
/templates/whatsapp を開いてテンプレートをクリックします。パネルが開き、ページの
アドレスに &template=wt-… が付きます — この wt- の短い ID をパスに入れます。
(テンプレートの UUID でも動作します。)
同じパネルに、呼び出しで何を埋める必要があるかが表示されます。
- 変数 — 本文の変数: 「2 個の位置変数」(
{{1}}、{{2}})か、 名前: に続く変数名のどちらかです。 - ヘッダー — そのテキスト、またはメディアヘッダーなら
[IMAGE]、[VIDEO]、[DOCUMENT]。 - ボタン — 順番に並び、それぞれに種類(
[URL]、[COPY_CODE]、[QUICK_REPLY])が付きます。最初のボタンがインデックス0です。
スクリプトからは、GET /api/wa-templates?status=approved で承認済みテンプレートを
shortId、name、language、category とともに一覧できます。
送信できるのは承認済みのテンプレートだけで、ほかのステータスは拒否されます。
リクエスト
ボディのフィールドは 2 つです。
| フィールド | 内容 |
|---|---|
to | 電話番号。E.164 形式 — +14255550123 |
components | Meta の template.components 配列そのもの。そのまま渡されます。変数のないテンプレートでは省略します |
components は Meta の Cloud API に送るのと同じ配列なので、Meta のドキュメントが
そのまま当てはまります。要点は次のとおりです。
- ヘッダー — パラメーター 1 つ: 変数を含むテキストヘッダーなら
text、またはhttps://のlinkを持つimage、video、document。 - 本文 —
textパラメーター。位置指定のテンプレート({{1}}、{{2}})では、 名前なしで順番どおりに送ります。名前付きのテンプレート({{customer_name}}) では、すべてのパラメーターにparameter_nameを付けます。両者を混ぜないで ください。Octo が作ったテンプレートは、どんな名前を付けたかにかかわらず、ここでは 位置指定です。 - ボタン — 値を受け取るボタンごとにコンポーネントを 1 つ。
sub_typeとindex(テンプレートのボタン一覧での位置)を付けます:url—textパラメーター 1 つ: リンクの変わる部分。copy_code— コード。Meta の{ "type": "coupon_code", "coupon_code": "FRESH20" }形式、または{ "type": "text" }。最大 15 文字。quick_reply— 任意。タップされたときに payload を受け取りたい場合は{ "type": "payload" }パラメーター。
完全な例
タイドウォーター・ランドリーの集荷テンプレート pickup_confirmation は、本文が
Hi {{1}}, your pickup is confirmed for {{2}}. Reply here to change it. で、
リンクが注文番号で終わる Track pickup ボタンが 1 つあります。
curl -X POST "https://api.omniocto.com/api/wa-templates/wt-a1b2c3d4e5/send" \
-H "Authorization: Bearer $OMNI_OCTO_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"to": "+14255550123",
"components": [
{ "type": "body", "parameters": [
{ "type": "text", "text": "Ana" },
{ "type": "text", "text": "Tuesday, 9–11am" }
] },
{ "type": "button", "sub_type": "url", "index": "0",
"parameters": [{ "type": "text", "text": "TW-20931" }] }
]
}'
テンプレートが名前付き変数を使っていれば、本文は各値に名前を付けます。
{ "type": "body", "parameters": [
{ "type": "text", "parameter_name": "customer_name", "text": "Ana" },
{ "type": "text", "parameter_name": "pickup_slot", "text": "Tuesday, 9–11am" }
] }
成功は 201 です。
{
"messageId": "6f1c…",
"conversationId": "3a9e…",
"conversationShortId": "cv-9f8e7d6c5b",
"externalId": "wamid.HBgL…",
"isNewConversation": false,
"template": { "id": "…", "shortId": "wt-a1b2c3d4e5", "name": "pickup_confirmation", "language": "en_US" }
}
externalId は、Meta が配信通知と既読通知を報告するときに使う ID で、これらの通知は
後から会話に届きます。201 は Meta が送信を受け付けたという意味で、電話が受信した
という意味ではありません。 conversationShortId は保存しておいてください — 後で
使います。
メッセージの届く場所
/conversations の、テンプレートの WhatsApp 番号上でその電話番号と交わしている 会話 — お客様のメッセージが届くのと同じスレッドです。
- その番号との会話がすでにあれば、テンプレートはそこに加わります — 会話が
クローズされていれば、再オープンされます。
isNewConversationはfalseです。 - なければ新しい会話が開かれます(未知の番号なら連絡先も作成されます)。
isNewConversationはtrueです。 - 吹き出しには値が入った本文が表示され、テンプレート · とテンプレート名のタグが 付きます。
お客様が返信すると、その返信は同じスレッドに届きます — そして、API による送信で スレッドが人に引き渡されることはありません。すでにあった会話は、それまで担当して いた人(またはエージェント)がそのまま担当します。クローズされていた会話や新しい 会話は、その WhatsApp 番号に応答する AI エージェントのもとでアクティブになります。 つまり、その番号にエージェントがいれば、人が /conversations で スレッドを引き継いで いない限り、そのエージェントが返信に答えます。
送信が拒否されたとき
| ステータス | code | 意味 |
|---|---|---|
| 400 | — | to が E.164 形式でない、または components が Meta の受け付ける形になっていない。ボディに問題点が並びます |
| 402 | billing_not_eligible | ワークスペースがまだ送信メッセージを送れない |
| 404 | template_not_found | このワークスペースにその ID のテンプレートがない |
| 409 | blocked_dnc | 番号が配信を停止している。メッセージはブロック済みとしてスレッドに残り、Meta には何も届きません |
| 422 | template_not_approved | Meta がテンプレートを承認していない。status で現在の状態がわかります |
| 422 | template_components_mismatch | components がテンプレートに合っていない。error が問題を示します |
| 422 | channel_unavailable | テンプレートの WhatsApp 番号が削除されたか無効になっている |
| 422 | channel_pending_verification, workspace_suspended, route_blocked | 番号またはワークスペースに対する送信チェックが拒否した。メッセージはブロック済みまたは失敗としてスレッドに残ります |
| 429 | workspace_rate_limited | ワークスペースがこのエンドポイントのレートを超えている。何も書き込まれません |
| 429 | rate_limited | WhatsApp 番号のバースト枠がいっぱい — 数秒から数分待ちます |
| 429 | daily_limit_reached | 番号の 1 日のテンプレート上限を使い切った — 待ち時間は数時間になることがあります |
| 502 | send_failed | Meta が送信を拒否した。error に Meta のメッセージが入ります。メッセージは失敗としてスレッドに残ります |
429 にはすべて Retry-After ヘッダーと、ボディに同じ値の retryAfterSec が
付きます。その時間待ってから同じ呼び出しを送り直してください。最初の呼び出しでは
メッセージは送信も保存も課金もされていません。(新しい番号への最初の送信が番号の
上限で拒否された場合は、会話だけが空のまま作られています。ボディにその
conversationShortId が入ります。)
2 種類の 429 は別々の上限です。ワークスペースは、デフォルトでこのエンドポイントを 毎分 300 回、毎秒最大 10 回のバーストで呼び出せます。このレートは API 呼び出しだけを 数え、Omni Octo のサポートがワークスペースごとに設定します。WhatsApp 番号には 独自の送信枠があり、受信トレイ、Octo、キャンペーンと共有されます — 1 つの番号はどれだけ速く送れるか で説明しています。
不一致なら何も送られない
呼び出しは、何かを書き込む前に components をテンプレートと照合します。値の
不足、余分な値、未知の名前、誤った種類のヘッダー、値を取らないボタンへの
パラメーター — いずれも 422 template_components_mismatch で、メッセージは保存
されず、何も送られません。error が何が問題かを正確に伝えます。
body expects 2 parameters, got 1
body is missing variable "pickup_slot"
button 0 is a QUICK_REPLY button, got url
button 0 (a URL button) is missing its url parameter
つまり、不一致は直して送り直しても安全です。
呼び出しは冪等ではない
成功した呼び出しは、1 回ごとにテンプレートを 1 通送り、課金されます。冪等キーは ありません。呼び出しがタイムアウトしたり接続が切れたりすると、送られたかどうか わからず、やみくもに再試行すると 2 回送られることがあります。
タイムアウト後に再試行する前に、スレッドを確認してください —
GET /api/conversations/{conversationShortId}
その番号への以前の送信で得た ID を使うか、/conversations で会話を開きます。 テンプレートがそこになければ、そのときだけ送り直してください。
上の拒否は事情が違います。4xx は送信が行われなかったことを意味します。
このエンドポイントでできないこと
- 自由文の送信。 送れるのは承認済みテンプレートだけです — 窓の中での返信には 受信トレイを 使ってください。
- リストへの送信。 1 回の呼び出しは 1 つの番号です。リストは キャンペーンが扱います。
- Meta の判断を覆すこと。 201 の後でも、マーケティング用テンプレートは Meta に 拒否されることがあります — 理由。
次に読む
- テンプレートを Omni Octo に取り込む — テンプレートは先に承認され、同期されている必要があります。
- 1 つの番号はどれだけ速く送れるか
—
rate_limitedとdaily_limit_reachedの背後にある番号ごとの枠。