Skip to content

Send

7 min read

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

FieldRequiredDescription
keysYesChannel tokens — whatsapp, telegram, waba, email
contentYesPer-channel message — template ID or inline message
targetYesRecipient address per channel + optional variables
contextNoScheduling 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.typeChannelsNotes
textWhatsApp, Telegram, WABAPlain text (WABA: approved session rules apply). Telegram buttons attach here via richContext.buttons.
image, video, documentWhatsApp, Telegram, WABARequires richContext.fileUrl or richContext.fileId. Telegram may also include buttons.
locationWhatsApp, TelegramRequires richContext.locationmessage text is ignored
interactiveWABASession reply buttons or list — see Buttons. Not used for Telegram (use text + buttons).

message.richContext (optional):

FieldPurpose
fileUrlPublic HTTPS URL to an attachment
fileIdID of a file uploaded to Resources → Files
fileNameDisplay filename — Kirisan may append the detected extension
footerFooter text (where supported)
location{ "lat", "lng", "name", "address" } for location messages
buttonsButton rows — shape depends on channel (see Buttons)
listMessageWABA 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:

typeExtra fieldsBehaviour
urlurlOpens a link (http / https / tg://)
callbackcallbackDataSends callback data to your bot (max 64 bytes)
copycopyTextCopies 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 buttonsrichContext.buttons with id + text only (max 3 total; Meta reply buttons). No template required.
  • ListrichContext.listMessage with a menu button label and sections / 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 buttonMeta typeNotes
Quick replyquick_replyUser taps → you receive a reply payload
URLurlOpens a link; may include one {{variable}}
Callphone_numberStarts a phone call
PathNeeds approved template?Session window?Button kinds
Free-form interactiveNoRequired (open window)Reply (id/text) or list
content.waba.templateYesNot requiredQuick 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"
    }
  }
}
FieldFormat
whatsapp, wabaPhone with country code (digits, no + required)
telegramChat ID
emailValid email address
variablesReplaces {{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"
    }
  }
}
FieldMeaning
timing.scheduleUnix 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:

  1. Primary — all keys with no fallback (sent together).
  2. Fallback 1 — keys with fallback_sequence: 1 (parallel within the tier).
  3. 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 statusMeaning
trueAt least one channel delivered or was scheduled, or the async request was queued
falseEvery 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

ChannelInline messageTemplates
WhatsAppYes — text, media, location (no interactive buttons)Yes — must be approved and active
TelegramYes — text/media + inline keyboard (url / callback / copy)Yes — same button kinds in the template editor
WABAYes — text, media, interactive reply buttons or list (session window)Yes — Meta templates with quick reply / URL / call; manage in Channel → WABA → Templates
EmailNo — use content.email.template onlyYes — 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.