Bỏ qua tới nội dung

Webhook

Deplao POST sự kiện tới URL của bạn ngay khi có. Bạn không cần giữ kết nối; Deplao tự thử lại khi server của bạn lỗi.

Đăng ký

bash
curl -X POST http://127.0.0.1:9900/api/v1/webhooks -H "Authorization: Bearer dpk_xxx" -H "Content-Type: application/json" -d '{"name":"CRM sync","url":"https://crm.example.com/deplao","events":["message.received","payment.received"]}'

Kết quả có secret dạng whsec_..., chỉ hiện một lần. Lưu lại để kiểm tra chữ ký. Gửi thử: POST /api/v1/webhooks/{id}/test.

Deplao gửi gì

http
POST /deplao HTTP/1.1
Host: crm.example.com
Content-Type: application/json
User-Agent: Deplao-Webhook/1
X-Deplao-Event: message.received
X-Deplao-Delivery: dlv_3c9e5a0f6d2b4e7a9c1d8f20
X-Deplao-Timestamp: 1760412345
X-Deplao-Signature: v1=5d0c7f...e1

{"id":"evt_...","seq":1532,"type":"message.received","ts":1760412345678,"account_id":"0501234567","channel":"zalo","data":{...}}
  • Thành công là HTTP 2xx trong 10 giây. Hãy trả 200 ngay rồi xử lý sau (đưa vào hàng đợi).
  • Redirect (3xx) không được đi theo và bị tính là lỗi. Hãy đăng ký URL cuối cùng.
  • Thử lại sau 30 giây, 2 phút, 10 phút, 1 giờ, 6 giờ (tổng 6 lần), sau đó chuyển failed. Gửi lại tay: POST /api/v1/webhook-deliveries/{id}/retry.
  • 100 lần lỗi liên tiếp thì webhook tự tắt. Bật lại bằng PATCH /api/v1/webhooks/{id} với {"active": true}.
  • Có thể nhận trùng và sai thứ tự khi có thử lại. Khử trùng bằng header X-Deplao-Delivery hoặc id, sắp xếp bằng seq.
  • Webhook dùng quyền của key tạo ra nó: chỉ nhận sự kiện của tài khoản key được cấp; key bị thu hồi thì ngừng gửi.
  • URL nội bộ (localhost, 192.168.x, 10.x) chỉ được dùng khi tạo webhook từ chính máy chạy Deplao (không qua tunnel), hoặc khi bật api_allow_private_urls.

Kiểm tra chữ ký

Chữ ký là v1= + hex(HMAC-SHA256(secret, timestamp + "." + body gốc)). Luôn dùng body gốc (chưa parse JSON), so sánh an toàn thời gian, và từ chối timestamp lệch quá 5 phút.

Node.js (Express):

js
import express from 'express';
import crypto from 'node:crypto';

const SECRET = process.env.DEPLAO_WEBHOOK_SECRET;
const app = express();

app.post('/deplao', express.raw({ type: 'application/json' }), (req, res) => {
  const ts = req.get('X-Deplao-Timestamp') || '0';
  const sig = req.get('X-Deplao-Signature') || '';
  const expected = 'v1=' + crypto.createHmac('sha256', SECRET).update(ts + '.' + req.body.toString('utf8')).digest('hex');
  const fresh = Math.abs(Date.now() / 1000 - Number(ts)) < 300;
  const valid = sig.length === expected.length && crypto.timingSafeEqual(Buffer.from(sig), Buffer.from(expected));
  if (!fresh || !valid) return res.status(401).end();

  res.status(200).end(); // trả lời ngay
  const event = JSON.parse(req.body.toString('utf8'));
  console.log(event.type, event.seq); // xử lý sau, ví dụ đưa vào hàng đợi
});

app.listen(3000);

Python (Flask):

python
import hashlib, hmac, os, time
from flask import Flask, abort, request

SECRET = os.environ["DEPLAO_WEBHOOK_SECRET"].encode()
app = Flask(__name__)

@app.post("/deplao")
def deplao():
    ts = request.headers.get("X-Deplao-Timestamp", "0")
    sig = request.headers.get("X-Deplao-Signature", "")
    body = request.get_data()  # bytes gốc
    expected = "v1=" + hmac.new(SECRET, ts.encode() + b"." + body, hashlib.sha256).hexdigest()
    if abs(time.time() - int(ts)) > 300 or not hmac.compare_digest(sig, expected):
        abort(401)
    event = request.get_json()
    print(event["type"], event["seq"])
    return "", 200

PHP:

php
<?php
$secret = getenv('DEPLAO_WEBHOOK_SECRET');
$body = file_get_contents('php://input');
$ts = $_SERVER['HTTP_X_DEPLAO_TIMESTAMP'] ?? '0';
$sig = $_SERVER['HTTP_X_DEPLAO_SIGNATURE'] ?? '';
$expected = 'v1=' . hash_hmac('sha256', $ts . '.' . $body, $secret);
if (abs(time() - (int)$ts) > 300 || !hash_equals($expected, $sig)) {
    http_response_code(401);
    exit;
}
$event = json_decode($body, true);
http_response_code(200);
// xử lý $event['type'], $event['data']

Cloudflare Worker (Web Crypto):

js
export default {
  async fetch(request, env) {
    const body = await request.text();
    const ts = request.headers.get('X-Deplao-Timestamp') || '0';
    const hex = (request.headers.get('X-Deplao-Signature') || '').replace('v1=', '');
    const enc = new TextEncoder();
    const key = await crypto.subtle.importKey('raw', enc.encode(env.DEPLAO_WEBHOOK_SECRET), { name: 'HMAC', hash: 'SHA-256' }, false, ['verify']);
    const sig = new Uint8Array((hex.match(/../g) || []).map((h) => parseInt(h, 16)));
    const ok = await crypto.subtle.verify('HMAC', key, sig, enc.encode(ts + '.' + body));
    if (!ok || Math.abs(Date.now() / 1000 - Number(ts)) > 300) return new Response('bad signature', { status: 401 });
    const event = JSON.parse(body);
    // ghi vào Queue / KV / D1 rồi xử lý sau
    return new Response('ok');
  },
};

Chưa tìm thấy câu trả lời?

Nhắn hỗ trợ qua Telegram