Help centerAdminDecision maker
Outbound Dispatch API: send proactive messages from your backend
Queue WhatsApp/Telegram sends with the same worker as the Outbound page. Auth, single/batch send, Meta templates, opt-outs, limits, and receipts.
The Outbound Dispatch API lets your backend queue proactive messages on a workspace’s connected channels — the same queue, pacing, suppression list, and audit trail as the in-app Outbound page. Use it from CRMs, order systems, and schedulers when you need server-triggered sends at scale.
Base URL and auth
https://concierge.bentokit.ai/api/integrations/concierge/v1/dispatch- Authorization: Bearer czsk_… (workspace API key from API Access → Keys)
- Every call is scoped to that key’s workspace
- Sends are asynchronous: validate + enqueue now; a paced worker delivers later
- Optional Idempotency-Key header (or body idempotencyKey) dedupes retries
curl -sS -X POST "https://concierge.bentokit.ai/api/integrations/concierge/v1/dispatch" \
-H "Authorization: Bearer czsk_…" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: order-4958343-shipped" \
-d '{ "channelId": "whatsapp-2", "to": "5521999999999", "text": "Olá!" }'Console twin
- Create an API key
API Access → Keys. Copy the secret once (czsk_…). Store it only on your server.
- Connect an outbound-capable channel
WhatsApp Cloud, WhatsApp Personal, Telegram, etc. The channel must support proactive send (Outbound page shows ready routes).
- Rehearse in Outbound (optional)
Compose a single send in the console first. The API uses the same worker and suppression list.
- Call the API from your backend
Prefer approved provider templates for official WhatsApp Cloud. Free-form text works on channels that allow it.
List recent dispatches
GET / with optional status, batchId, and limit (1–500, default 50) returns recent dispatch rows for the workspace. Status values include queued, sending, accepted, sent, delivered, read, failed, blocked, expired, cancelled.
Send one message
POST / returns 202 with { dispatch } when queued, 409 when blocked (e.g. opted out), or { dispatch, deduped: true } when an idempotency key matches an existing row. Exactly one content shape is required.
{
"channelId": "whatsapp-2",
"to": "5521999999999",
"ttlSeconds": 86400,
"text": "…",
"templateId": "tpl_…",
"variables": { "name": "Ana" },
"providerTemplate": {
"name": "order_update",
"language": "pt_BR",
"parameters": ["Ana", "4958343"]
},
"idempotencyKey": "optional-stable-key"
}- text: free-form (channels that allow free-form outbound)
- templateId + variables: workspace saved message with {{named}} placeholders
- providerTemplate: approved Meta-style template (required for official WhatsApp Cloud)
- ttlSeconds: 60–604800 (default 86400). Expired-in-queue is discarded, never sent late
- Omit channelId only when the workspace has exactly one outbound channel; otherwise pass it or you get channel_ambiguous
Batch send
POST /batch accepts the same content shapes with recipients: [{ to, variables? | parameters? }]. Up to 500 recipients per request. A tenant-scoped batch idempotency key prevents double-enqueue on retries.
{
"channelId": "whatsapp-2",
"providerTemplate": {
"name": "order_update",
"language": "pt_BR",
"parameters": ["PLACEHOLDER"]
},
"recipients": [
{ "to": "5521999999999", "parameters": ["Ana", "4958343"] },
{ "to": "5521888888888", "parameters": ["Bruno", "4958344"] }
],
"ttlSeconds": 86400
}Recipients from Concierge leads
GET /recipients?channelId=…&q=…&limit=… projects captured leads as sendable phone recipients for that channel (normalized id, name, phone, email, lead source/status, optedOut). Phone-kind channels only; other kinds return unsupportedKind: true until chat-id mapping exists.
Saved messages (workspace templates)
- GET|POST /templates — list or create named bodies with {{variable}} placeholders
- GET|PUT|DELETE /templates/:id — read, update, delete
- Usable only on channels that allow free-form / saved-message outbound
Provider (Meta) templates
- GET /channels/:channelId/templates[?includeUnavailable=true] — approved by default, or all statuses
- POST /channels/:channelId/templates — submit a new template when the channel declares templateManagement
- DELETE /channels/:channelId/templates/:name — delete all language versions for that name (provider semantics)
{
"name": "order_update",
"language": "pt_BR",
"category": "UTILITY",
"bodyText": "Olá {{1}}, seu pedido {{2}} está a caminho.",
"bodyExamples": ["Ana", "4958343"],
"footerText": "BentoKit"
}Validation failures return 400 with stable error codes (invalid_template_name, template_placeholders_not_sequential, template_examples_mismatch, …). New templates are often status pending until Meta review finishes.
Suppression list (opt-outs)
- GET /optouts — list suppressed recipients
- POST /optouts — { to, channelId? | channelKind? } blocks before enqueue
- DELETE /optouts?to=…&channelKind=… — remove a suppression
- Honor the same list in your own systems so you do not re-target people who opted out
Limits and errors
- Body text max ~4000 characters
- Batch max 500 recipients
- Default daily dispatch cap 1000/workspace (env CONCIERGE_DISPATCH_DAILY_CAP) → 429 daily_dispatch_cap
- Enqueue burst limit (default 120/min) → 429 rate_limited
- Common 4xx: channel_not_found, no_dispatch_channel, channel_ambiguous, template_or_text_required, provider_template_not_approved, recipient_opted_out (blocked)
- 402 plan_feature_missing when outbound_campaigns is not on the plan
Delivery receipts back to you
If you need your system to learn when a message was accepted, delivered, or failed, subscribe to dispatch.* on API Access → Webhooks (optional). Most CRM integrations can skip those events and poll GET / instead.
- API key stored only on the server
- Official WhatsApp uses approved providerTemplate only
- Idempotency-Key on every production send that may retry
- Suppression list checked or mirrored in your CRM
- TTL set so late channel outages do not send stale promos