For developers
Channel API guide
The Channel API lets your own app or system — your shop app, a website with its own chat UI, Zapier/Make/n8n — be served by Rspond like WhatsApp: messages land in Chat, get answered by your bot/AI, can be taken over by your team, count toward your quota, and can create tickets or orders.
±30 minutes to integrate · a little code on your server
No developer? Use Zapier, Make, or n8nHow it works
1. Send messages
When a user writes in your app, your server sends the message to Rspond (POST).
2. Rspond handles it
The message shows in Chat and is answered by your bot/AI or team — just like other channels.
3. Receive replies
Rspond sends replies to your webhook URL (signed), or you fetch them by polling.
1. Set up in Rspond
- Make sure the API channel is in your workspace plan (Billing → pick API as your plan's channel or an extra channel).
- Open Channels → API → Create API connection. Enter a reply webhook URL if your server can receive POSTs from the internet (public HTTPS); leave it empty to use polling.
- Copy the Channel secret (rspch_…) — it's shown only once. Keep it on your server as an environment variable, e.g. RSPOND_CHANNEL_SECRET. Never put it in a mobile app or browser code.
- Copy the inbound message endpoint (…/api/channel/ch_…/messages).
- Attach a bot to the API connection in the Bots menu.
2. Send user messages to Rspond
POST to the inbound endpoint with the header Authorization: Bearer <channel secret> and a JSON body:
| Field | Required | Description |
|---|---|---|
| user_id | required | The user's id in your system (1–128 characters: letters, digits, . _ : @ + -). Keep it stable per person — Rspond turns it into one contact and one conversation. |
| text | one of | Message text, up to 4000 characters. |
| image | one of | A photo as a JPEG/PNG data URL (data:image/jpeg;base64,…), up to ±3 MB. |
| payload | one of | The id of the button the user tapped (see Buttons). |
| name | optional | The user's display name in the inbox, up to 80 characters. |
| message_id | recommended | A unique message id from your system. If a request is sent twice, the message isn't recorded twice. |
curl -X POST "https://rspond.id/api/channel/ch_KUNCI_ANDA/messages" \
-H "Authorization: Bearer $RSPOND_CHANNEL_SECRET" \
-H "Content-Type: application/json" \
-d '{"user_id":"u-1024","name":"Budi","text":"Is the Gayo coffee in stock?","message_id":"chat-88121"}'Responses
| Status | Meaning | Details |
|---|---|---|
| 200 | Accepted | { "ok": true, "duplicate": false, "conversation_id": "…" } |
| 400 | Invalid body | The error message names the field that's wrong. |
| 401 | Wrong secret | Check the Authorization header. |
| 404 | Unknown connection | Wrong key in the URL, the connection was disconnected, or the API channel is off. |
| 429 | Too many messages | Wait for the Retry-After header, then resend. |
3. Receive replies via webhook
Every bot, AI, or team reply is POSTed as JSON to your webhook URL. The Rspond-Signature header holds t (UNIX time) and v1 = HMAC-SHA256(channel secret, "t.body") in hex. Verify the signature before using the body, and reject t older than 5 minutes.
POST https://sistem-anda.com/rspond/webhook
Content-Type: application/json
Rspond-Signature: t=1792390000,v1=5d41402abc4b2a76b9719d911017c592...
{
"event": "message.reply",
"id": "msg_3f1c2a5e-8b7d-4c1e-9a0f-2d6b7e8c9a10",
"user_id": "u-1024",
"text": "Ada, Kak! Kopi Arabika Gayo 250g harganya Rp85.000. Mau saya pesankan?",
"buttons": [
{ "id": "pesan", "label": "Pesan sekarang" },
{ "id": "admin", "label": "Bicara dengan admin" }
],
"media": null,
"sent_at": "2026-10-19T03:01:12.000Z"
}- Respond with a 2xx status within 8 seconds. Failures/timeouts/5xx are retried up to 3 times (immediately, +1 s, +3 s); 4xx isn't retried.
- If every attempt fails, the message is marked failed in the Rspond inbox so your team sees it. Replies can still be fetched by polling.
- media.url is valid for about 1 hour — download and store it if you need to show it longer.
- event "message.reply" = a bot/AI/team reply; event "message.proactive" = a message you requested yourself via /send (section 5) — ignore it if your app already shows it.
Signature verification examples
import crypto from "node:crypto";
import express from "express";
const app = express();
// Body MENTAH dibutuhkan untuk memeriksa tanda tangan.
app.post("/rspond/webhook", express.raw({ type: "application/json" }), (req, res) => {
const header = req.get("Rspond-Signature") ?? "";
const { t, v1 = "" } = Object.fromEntries(header.split(",").map((p) => p.split("=")));
const body = req.body.toString("utf8");
const expected = crypto
.createHmac("sha256", process.env.RSPOND_CHANNEL_SECRET)
.update(`${t}.${body}`)
.digest("hex");
const fresh = Math.abs(Date.now() / 1000 - Number(t)) < 300;
const valid = v1.length === expected.length && crypto.timingSafeEqual(Buffer.from(v1), Buffer.from(expected));
if (!fresh || !valid) return res.sendStatus(401);
const reply = JSON.parse(body);
// show the reply to the user: reply.user_id, reply.text, reply.buttons, reply.media
res.sendStatus(200);
});4. Buttons (Chatbot menus)
If a reply has buttons, show them as buttons. When the user taps one, send a message with payload = the button id (text is optional, shown in the inbox):
{ "user_id": "u-1024", "payload": "pesan", "text": "Pesan sekarang" }5. Proactive messages (you start the chat)
Want to message a user first — order shipped, appointment tomorrow, invoice due? POST to the …/send endpoint (replace /messages in the URL with /send) with the same Authorization header. Rspond records it in the conversation (shown as "Your system (API)" in the inbox, and the AI understands it when the user replies), then delivers it to your webhook with event "message.proactive" — or makes it available by polling.
| Field | Required | Description |
|---|---|---|
| user_id | required | The recipient. Never chatted before? A contact and conversation are created for you. |
| text | one of | Message text, up to 4000 characters. |
| image | one of | A photo as a JPEG/PNG data URL, up to ±3 MB. |
| buttons | optional | Up to 10 buttons { id, label } (label ≤40 characters), needs text. The user's tap comes back as a payload. |
| name | optional | The user's display name in the inbox. |
| message_id | recommended | A unique notification id from your system (e.g. order-88-shipped). Resent → not duplicated (duplicate: true). |
curl -X POST "https://rspond.id/api/channel/ch_KUNCI_ANDA/send" -H "Authorization: Bearer $RSPOND_CHANNEL_SECRET" -H "Content-Type: application/json" -d '{"user_id":"u-1024","text":"Order #88 has shipped via JNE (tracking JN123456).","buttons":[{"id":"lacak","label":"Track parcel"}],"message_id":"order-88-shipped"}'Responses
{ "ok": true, "duplicate": false, "id": "out:u-1024:order-88-shipped", "conversation_id": "…" }- A 200 response holds the message id (same as the id on the webhook). 502 = your webhook didn't accept it; the message is marked failed in the inbox — resend with the same message_id.
- Doesn't use the AI reply quota and doesn't change the conversation's status; if the user replies, the bot/AI or team carries on as usual.
- Limit of 120 proactive messages per minute per workspace (429 + Retry-After). For mass promotions, send in batches.
- Your webhook server must keep serving other requests while it calls /send — Rspond delivers the webhook before /send responds.
6. Photos
Send a user's photo (e.g. a payment proof) as a data URL. Photos from the bot/team arrive in media.url on the webhook.
{ "user_id": "u-1024", "image": "data:image/jpeg;base64,/9j/4AAQSkZJRg…", "text": "Ini bukti transfernya" }7. Alternative: polling (no webhook)
If your server can't receive webhooks, fetch one user's conversation history with a GET to the same endpoint. Use after (the ISO time of the last message you already have) so only new messages come back; up to 100 per call. Every 3–5 seconds while the user has the chat open works well.
curl "https://rspond.id/api/channel/ch_KUNCI_ANDA/messages?user_id=u-1024&after=2026-10-19T03:00:00Z" \
-H "Authorization: Bearer $RSPOND_CHANNEL_SECRET"Example
{
"messages": [
{ "id": "u-1024:chat-88121", "direction": "in", "text": "Kopi gayo masih ada?",
"buttons": null, "media": null, "created_at": "2026-10-19T03:01:05.000Z" },
{ "id": "msg_3f1c2a5e-…", "direction": "out", "text": "Ada, Kak! …",
"buttons": [{ "id": "pesan", "label": "Pesan sekarang" }], "media": null,
"created_at": "2026-10-19T03:01:12.000Z" }
]
}Tips & limits
- Keep the channel secret on your server only. Leaked? Create a new one in Channels → API — the old one stops working immediately.
- One user_id = one contact in Rspond. Use a permanent id from your database, not a session id.
- Your team can take over a conversation from the Rspond inbox or mobile app; team replies go to the same webhook.
- Every AI reply uses your workspace plan's AI reply quota (shared with your other channels).
- Rate limits protect your quota from message floods; resend after Retry-After when you get a 429.
Ready to try?
Create an API connection in the dashboard, then send a test message with the cURL example above.
Open Channels in the dashboard