Skip to content

Realtime events

There are 4 ways to receive events; pick one based on where your code runs:

Method Best for Notes
Socket.IO namespace /v1 Apps and workers that keep a long-lived connection Fastest; also carries conversation.typing
SSE GET /api/v1/events/stream Scripts, servers that can't install socket.io Works well through a tunnel
Polling GET /api/v1/events?since= Cron, serverless, Google Apps Script No connection to keep open
Webhook Servers with a public URL Deplao POSTs to you and retries automatically (see the "Webhooks" guide)

The key needs the events:subscribe scope (webhooks need webhooks:manage).

Event structure

json
{
  "id": "evt_6b1f0c2d9a4e4c1b8f3a2d10",
  "seq": 1532,
  "type": "message.received",
  "ts": 1760412345678,
  "account_id": "0501234567",
  "channel": "zalo",
  "data": {
    "message": { "msg_id": "7712345678901", "thread_id": "8765432109876", "thread_type": "user", "text": "Hi, do you still have size M?", "is_self": false },
    "conversation": { "thread_id": "8765432109876", "display_name": "Lan Nguyễn" }
  }
}
  • seq keeps increasing and acts as the "cursor" for catching up on missed events. Store the last seq you processed.
  • data.message has the same shape as GET .../messages, so one handler works for both.
  • conversation.typing is not stored (seq = 0) and is only available over Socket.IO and SSE.
  • Full catalog with descriptions: GET /api/v1/events/types (no key required).

Rules

  • A key only receives events for the accounts it has been granted. System-wide events (payment.received, plugin.*, ping) are only sent to keys without an account restriction.
  • Type filters: message.received (exact name), message.* (whole group), * (everything).
  • Deplao only records events when something can receive them: a key with an events/webhooks scope, an open connection, or an active webhook. Events that happened before you created your first key are not available.
  • Messages older than 5 minutes (history sync, loading older messages) do not emit message.received.
  • Events are kept for 7 days or the latest 50,000 records; change this in Tools → Apps, API & Webhook → API → Advanced (Công cụ → Ứng dụng, API & Webhook → API → Nâng cao).
  • Delivery is at least once: after a reconnect you may receive duplicates. Deduplicate by id or seq.

Socket.IO (Node.js)

js
// npm i socket.io-client@4
import { io } from 'socket.io-client';
import fs from 'node:fs';

let lastSeq = Number(fs.existsSync('seq.txt') ? fs.readFileSync('seq.txt', 'utf8') : 0);
const socket = io('http://127.0.0.1:9900/v1', {
  auth: { apiKey: process.env.DEPLAO_KEY },
  transports: ['websocket'],
});

socket.on('hello', ({ seq_now }) => {
  // First run starts from now; later runs also receive whatever was missed while disconnected.
  socket.emit('subscribe', { types: ['message.*'], since: lastSeq || seq_now }, (ack) => console.log('subscribed', ack));
});

socket.on('event', (ev) => {
  if (ev.type === 'message.received') {
    console.log(ev.data.conversation.display_name, ':', ev.data.message.text);
  }
  if (ev.seq) {
    lastSeq = ev.seq;
    fs.writeFileSync('seq.txt', String(lastSeq));
  }
});

socket.on('connect_error', (err) => console.error('Could not connect:', err.message));
  • Connection errors arrive via connect_error: UNAUTHORIZED (wrong key), FORBIDDEN_SCOPE (missing events:subscribe), FORBIDDEN_IP, API_DISABLED.
  • subscribe takes { types, account_ids, since }. Call subscribe again to change the filter; unsubscribe to stop.
  • Through a tunnel: io('https://<domain>/v1', ...) (default path /socket.io).

SSE

bash
curl -N -H "Authorization: Bearer dpk_xxx" "http://127.0.0.1:9900/api/v1/events/stream?types=message.*&since=1500"
text
id: 1532
event: message.received
data: {"id":"evt_...","seq":1532,"type":"message.received",...}
  • Reconnect with the Last-Event-ID: <seq> header or ?since=<seq> to receive what you missed.
  • A : ping line is sent every 15 seconds to keep the connection alive through proxies and tunnels.
  • The browser's EventSource cannot send an Authorization header. In the browser, use Socket.IO.

Python:

python
import json, requests

r = requests.get(
    "http://127.0.0.1:9900/api/v1/events/stream",
    params={"types": "message.received"},
    headers={"Authorization": "Bearer dpk_xxx"},
    stream=True, timeout=(5, 60),
)
for line in r.iter_lines(decode_unicode=True):
    if line and line.startswith("data: "):
        ev = json.loads(line[6:])
        print(ev["seq"], ev["data"]["message"]["text"])

Polling

bash
curl -H "Authorization: Bearer dpk_xxx" "http://127.0.0.1:9900/api/v1/events?since=0&types=message.received&limit=100"
json
{ "success": true, "data": [ { "seq": 1532, "type": "message.received" } ], "pagination": { "limit": 100, "has_more": false, "next_since": 1532 }, "request_id": "..." }

Call again with since = pagination.next_since. When has_more = true, call again right away; when it is false, wait a few seconds.

Didn't find your answer?

Message us on Telegram