Send
Endpoint
POST https://api.kirisan.com/v1/send
Authorization: Bearer YOUR_ACCOUNT_TOKEN
Content-Type: application/json Sends to one recipient per request. For large batches, use Send → Campaign in the dashboard or call this endpoint once per recipient from your app.
Request body
| Field | Required | Description |
|---|---|---|
keys | Yes | Channel tokens — whatsapp, telegram, waba, email |
content | Yes | Per-channel message — template ID or inline message |
target | Yes | Recipient address per channel + optional variables |
context | No | Scheduling and send behaviour |
keys
{
"keys": {
"whatsapp": {
"token": "DEVICE_TOKEN",
"fallback": false,
"fallback_sequence": 0
},
"email": {
"token": "SENDER_TOKEN",
"fallback": true,
"fallback_sequence": 1
}
}
} Include a key for every channel you intend to send on. See Authentication for where to copy tokens.
content
Each channel entry uses either a saved template or an inline message — not both.
Template reference (recommended — same as Send → Send):
{
"content": {
"whatsapp": { "template": 42 },
"email": { "template": 17 }
}
} Inline message (WhatsApp, Telegram, WABA only — email must use a template):
{
"content": {
"whatsapp": {
"message": {
"type": "text",
"message": "Hello {{name}}, your order is ready."
}
}
}
} message.type | Channels | Notes |
|---|---|---|
text | WhatsApp, Telegram, WABA | Plain text (WABA: approved session rules apply). Telegram buttons attach here via richContext.buttons. |
image, video, document | WhatsApp, Telegram, WABA | Requires richContext.fileUrl or richContext.fileId. Telegram may also include buttons. |
location | WhatsApp, Telegram | Requires richContext.location — message text is ignored |
interactive | WABA | Session reply buttons or list — see Buttons. Not used for Telegram (use text + buttons). |
message.richContext (optional):
| Field | Purpose |
|---|---|
fileUrl | Public HTTPS URL to an attachment |
fileId | ID of a file uploaded to Resources → Files |
fileName | Display filename — Kirisan may append the detected extension |
footer | Footer text (where supported) |
location | { "lat", "lng", "name", "address" } for location messages |
buttons | Button rows — shape depends on channel (see Buttons) |
listMessage | WABA list message object (button label + sections) |
Buttons
Kirisan does not treat “buttons” as one universal type. Channel and message path decide which kinds you can send.
Telegram — inline keyboard (url / callback / copy)
Use type: "text" (or media) with richContext.buttons as an array of rows. Each button needs text and a type:
type | Extra fields | Behaviour |
|---|---|---|
url | url | Opens a link (http / https / tg://) |
callback | callbackData | Sends callback data to your bot (max 64 bytes) |
copy | copyText | Copies text to the user’s clipboard |
{
"type": "text",
"message": "Choose an option:",
"richContext": {
"buttons": [
[
{ "text": "Open site", "type": "url", "url": "https://example.com" },
{ "text": "Confirm", "type": "callback", "callbackData": "confirm" }
],
[
{ "text": "Copy code", "type": "copy", "copyText": "PROMO-10" }
]
]
}
} Same three kinds are available when you build a keyboard under Channel → Telegram → Templates.
WABA — session interactive (reply buttons or list)
Free-form WABA messages (including interactive) do not need a Meta template, but they only work inside Meta’s customer-care / session window — typically after the user messaged you (same rule as free-form text and media). Outside that window, Meta rejects free-form sends; use an approved template instead.
When the session is open, type: "interactive" accepts:
- Reply buttons —
richContext.buttonswithid+textonly (max 3 total; Metareplybuttons). No template required. - List —
richContext.listMessagewith a menubuttonlabel andsections/rows. No template required.
Not available on this free-form path: template-style quick reply, URL, or call buttons.
{
"type": "interactive",
"message": "Need help with order {{order_id}}?",
"richContext": {
"footer": "Reply anytime",
"buttons": [[{ "id": "yes", "text": "Yes" }, { "id": "no", "text": "No" }]]
}
} WABA — Meta templates (quick reply / URL / call)
Use a template when you need to message outside the session window, or when you need quick reply / URL / call buttons.
Those three button kinds belong on a Meta-approved template, not on inline interactive JSON. Create them under Channel → WABA → Templates, then send with content.waba.template: <id>.
| Template button | Meta type | Notes |
|---|---|---|
| Quick reply | quick_reply | User taps → you receive a reply payload |
| URL | url | Opens a link; may include one {{variable}} |
| Call | phone_number | Starts a phone call |
| Path | Needs approved template? | Session window? | Button kinds |
|---|---|---|---|
Free-form interactive | No | Required (open window) | Reply (id/text) or list |
content.waba.template | Yes | Not required | Quick reply / URL / call (as defined on the template) |
WhatsApp (Fonnte device) does not support outbound interactive buttons on /v1/send.
target
{
"target": {
"whatsapp": "6281234567890",
"email": "customer@example.com",
"telegram": "123456789",
"waba": "6281234567890",
"variables": {
"name": "Alex",
"order_id": "A-1001"
}
}
} | Field | Format |
|---|---|
whatsapp, waba | Phone with country code (digits, no + required) |
telegram | Chat ID |
email | Valid email address |
variables | Replaces {{placeholders}} in templates and inline messages before send |
Omit channel fields you are not sending on. Each active channel in content needs a matching target (unless filtered by context.behaviour.channel).
context
{
"context": {
"timing": {
"schedule": 1735689600
},
"behaviour": {
"channel": "all",
"mode": "sync"
}
}
} | Field | Meaning |
|---|---|
timing.schedule | Unix timestamp (seconds) — queue for future delivery. Omit or set null to send now. Past times are rejected. |
behaviour.channel | "all" (default) — send every channel that has a key and content. Or one of whatsapp, telegram, waba, email to restrict to a single channel. |
behaviour.mode | "sync" (default) — wait for channel results in the HTTP response. "async" — queue immediately (pending in Kirisan logs); results appear in Logs/History automatically. Optional: also receive a POST to your send_status webhook if you configure one. |
When scheduled, the response includes "scheduled": true and each channel shows "scheduled": true instead of delivering immediately. Scheduling uses the scheduled path (not mode: async).
Examples
Every example is a full POST /v1/send request. Open a row for cURL, Node.js, Python, PHP, Go, or raw JSON. Replace tokens, IDs, and targets with your own values.
Fallback cascade
When channel keys use fallback: true, Kirisan tries tiers in order:
- Primary — all keys with no fallback (sent together).
- Fallback 1 — keys with
fallback_sequence: 1(parallel within the tier). - Fallback 2 — keys with
fallback_sequence: 2, and so on.
As soon as any channel in a tier succeeds, later tiers are skipped. This matches Use as fallback and Sequence on Send → Send. See Send messages for a dashboard walkthrough.
Failed channels in an earlier tier still appear under "channels" with "reason". A successful fallback channel may include "fallback": true.
Response
Immediate send (mode: sync or omitted):
{
"status": true,
"id": 98765,
"request_id": 12345,
"channels": {
"whatsapp": {
"status": true,
"id": 98765,
"processing_time": "142ms"
}
}
} id is send.id (integer — the primary Kirisan tracking key). request_id is send_log.id for this /v1/send request. id is omitted when no send row exists yet (e.g. blocked before any channel ran).
Async (mode: async) — accepted into the queue:
{
"status": true,
"queued": true,
"request_id": 12345
} Message id is not available at queue time — the final result is stored automatically in Kirisan logs (send / send_log). If you configure a send_status webhook, the same payload is also POSTed to your URL.
Partial failure:
{
"status": false,
"channels": {
"whatsapp": {
"status": false,
"reason": "Device disconnected",
"processing_time": "89ms"
},
"email": {
"status": true,
"fallback": true,
"processing_time": "891ms"
}
}
} Scheduled:
{
"status": true,
"scheduled": true,
"channels": {
"telegram": { "status": true, "scheduled": true }
}
} Top-level status | Meaning |
|---|---|
true | At least one channel delivered or was scheduled, or the async request was queued |
false | Every attempted channel failed, or validation blocked the request |
Read "reason" on the root for validation errors. Read "channels.<name>.reason" for delivery failures. See Errors.
Kirisan also stores the request + response in the audit log for validation/auth failures (invalid JSON, bad token, bad content, etc.) — the response includes request_id when the log row was written, so support can look up your request.
Channel notes
| Channel | Inline message | Templates |
|---|---|---|
| Yes — text, media, location (no interactive buttons) | Yes — must be approved and active | |
| Telegram | Yes — text/media + inline keyboard (url / callback / copy) | Yes — same button kinds in the template editor |
| WABA | Yes — text, media, interactive reply buttons or list (session window) | Yes — Meta templates with quick reply / URL / call; manage in Channel → WABA → Templates |
No — use content.email.template only | Yes — create in Channel → Email → Templates |
Templates referenced by ID must be Approved and Active. List IDs via the Templates API (GET /v1/templates), copy from Channel → channel → Templates, or use inline message content instead.
Related
- Authentication — account token and channel keys
- Templates API — list and fetch templates
- Errors — HTTP codes and common reasons
- Send status webhook — async
POST /v1/sendresults - Send messages — same request shape from the UI
- WhatsApp templates — create templates and copy IDs for send