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-Deliveryhoặcid, sắp xếp bằngseq. - 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ậtapi_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 "", 200PHP:
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