Hướng dẫn AgentFlow

Ví dụ: webhook

Tạo một đăng ký webhook, nhận và xác minh một lần gửi, đọc hồ sơ mà sự kiện nhắc tới, rồi xem lại các lần gửi, gửi lại và xoay khóa ký.

Các lệnh dưới đây gọi API với tư cách một chủ thể dịch vụ:

export ORIGIN=https://agentflow.example.com
export AGENTFLOW_KEY=afk_…
export SP_ID=3c9a…           # chính chủ thể dịch vụ của khóa này

Phản hồi đã được rút gọn; … đánh dấu phần lược bỏ. Khái niệm nền nằm ở Sự kiện và webhook.

1. Chọn loại sự kiện

Danh mục nói rõ mỗi loại sự kiện nói về cái gì:

curl -sS "$ORIGIN/api/v1/webhook-event-types?q=record." \
  -H "Authorization: Bearer $AGENTFLOW_KEY"
{
  "items": [
    { "type": "record.created", "subject_kind": "record", "description": "A record was created." },
    { "type": "record.updated", "subject_kind": "record", "description": "A record's fields changed. …" },
    …
  ],
  "next_cursor": null,
  "prev_cursor": null
}

2. Tạo đăng ký

Một chủ thể dịch vụ chỉ được đặt tên chính nó. Dùng Idempotency-Key để lần gửi lại không tạo ra đăng ký thứ hai:

curl -sS -X POST "$ORIGIN/api/v1/webhook-subscriptions" \
  -H "Authorization: Bearer $AGENTFLOW_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d "{
        \"url\": \"https://hooks.ketoan.example/agentflow\",
        \"event_types\": [\"record.created\", \"record.updated\"],
        \"service_principal_id\": \"$SP_ID\",
        \"description\": \"Đồng bộ hồ sơ sang hệ thống kế toán\"
      }"
{
  "id": "9b31…",
  "url": "https://hooks.ketoan.example/agentflow",
  "event_types": ["record.created", "record.updated"],
  "service_principal_id": "3c9a…",
  "status": "active",
  "principal_disabled_at": null,
  "held_delivery_count": 0,
  "secret": "whsec_0p3Fh…"
}

secret chỉ hiện đúng một lần. Hãy lưu ngay vào kho bí mật của bạn; đọc lại đăng ký sẽ không còn nó. Mất khóa thì xoay khóa chứ không đọc lại được.

Một lần gửi lại với cùng khóa trả về đúng đăng ký ấy, với "secret": null — khóa không bao giờ hiện lần thứ hai.

3. Người nhận: xác minh rồi trả lời ngay

import { createHmac, timingSafeEqual } from 'node:crypto';
import express from 'express';

const app = express();
// Cần thân nguyên văn để ký — đừng dùng express.json() ở tuyến này.
app.post('/agentflow', express.raw({ type: '*/*' }), (req, res) => {
  const raw = req.body.toString('utf8');
  const key = Buffer.from(process.env.WEBHOOK_SECRET.replace(/^whsec_/, ''), 'base64');
  const signed = `${req.get('webhook-id')}.${req.get('webhook-timestamp')}.${raw}`;
  const want = createHmac('sha256', key).update(signed).digest('base64');

  const ok = (req.get('webhook-signature') ?? '').split(' ').some((part) => {
    const got = Buffer.from(part.replace(/^v1,/, ''), 'base64');
    const exp = Buffer.from(want, 'base64');
    return got.length === exp.length && timingSafeEqual(got, exp);
  });
  if (!ok) return res.sendStatus(401);

  // Đưa vào hàng đợi của bạn rồi trả lời ngay: yêu cầu chỉ có 10 giây.
  enqueue(JSON.parse(raw));
  res.sendStatus(204);
});

4. Một lần gửi trông thế nào

POST /agentflow HTTP/1.1
user-agent: AgentFlow-Webhooks/1
webhook-id: 0199a3f1-9c2e-7b41-9c3d-2f5a8b1e4d70
webhook-timestamp: 1789460000
webhook-signature: v1,K5oT8s…=
content-type: application/json

{
  "id": "0199a3f1-9c2e-7b41-9c3d-2f5a8b1e4d70",
  "type": "record.updated",
  "occurred_at": "2026-09-15T08:13:20.123456Z",
  "org_id": "0a5f…",
  "position": 1843221,
  "subject": { "kind": "record", "id": "7d2e…", "entity_id": "0a5f…" },
  "changed_fields": ["so_tien"],
  "record_version": 42
}

Phong bì chỉ mang định danh và tên trường — không bao giờ mang giá trị.

5. Đọc hồ sơ mà sự kiện nhắc tới

Giá trị nằm ở hồ sơ, và bạn đọc nó bằng chính khóa của mình:

curl -sS "$ORIGIN/api/v1/entities/0a5f…/records/7d2e…" \
  -H "Authorization: Bearer $AGENTFLOW_KEY"

So record_version bạn vừa đọc với record_version trong phong bì: nếu hồ sơ đã nhảy lên phiên bản cao hơn, bạn đang đọc bản mới hơn sự kiện — và đó là bản đúng để ghi vào hệ thống của bạn.

6. Khi có trục trặc: xem lần gửi và lịch sử

Những lần gửi chưa xong hoặc đã hỏng:

curl -sS "$ORIGIN/api/v1/webhook-subscriptions/9b31…/deliveries?status=pending,failed" \
  -H "Authorization: Bearer $AGENTFLOW_KEY"
{
  "items": [
    {
      "id": "c71a…",
      "event_id": "0199a3f1…",
      "event_type": "record.updated",
      "status": "pending",
      "attempt_count": 3,
      "retry_deadline_at": "2026-09-16T08:13:20Z",
      "next_attempt_at": "2026-09-15T11:21:04Z",
      "redelivery_of": null
    }
  ],
  …
}

Một lần gửi cụ thể kèm từng lần thử — mã trạng thái, thời lượng, lý do:

curl -sS "$ORIGIN/api/v1/webhook-subscriptions/9b31…/deliveries/c71a…" \
  -H "Authorization: Bearer $AGENTFLOW_KEY"
{
  "id": "c71a…",
  "status": "pending",
  "attempts": [
    { "attempt_no": 1, "outcome": "timeout", "status_code": null, "duration_ms": 10003, "detail": "…" },
    { "attempt_no": 2, "outcome": "http_error", "status_code": 502, "duration_ms": 431, "detail": "…" }
  ]
}

Còn lịch sử cho biết đã mất gì trong lúc bạn ngừng hoạt động:

curl -sS "$ORIGIN/api/v1/webhook-subscriptions/9b31…/history" \
  -H "Authorization: Bearer $AGENTFLOW_KEY"

Một mục deliveries_expired kèm số lượng theo loại sự kiện và khoảng thời gian cũ nhất — mới nhất; dựa vào đó mà đọc lại trạng thái hiện tại của những hồ sơ trong khoảng ấy.

7. Gửi lại một sự kiện

curl -sS -X POST \
  "$ORIGIN/api/v1/webhook-subscriptions/9b31…/deliveries/c71a…/redeliver" \
  -H "Authorization: Bearer $AGENTFLOW_KEY" \
  -H "Idempotency-Key: $(uuidgen)"

Trả 202 kèm lần gửi mới, redelivery_of trỏ về lần cũ, và cùng webhook-id — nên người nhận đã khử trùng lặp đúng cách sẽ bỏ qua nếu họ đã xử lý rồi. Sự kiện quá 30 ngày nhận 410 WEBHOOK_EVENT_NOT_RETAINED.

8. Xoay khóa ký

curl -sS -X POST "$ORIGIN/api/v1/webhook-subscriptions/9b31…/rotate-secret" \
  -H "Authorization: Bearer $AGENTFLOW_KEY" \
  -H "Content-Type: application/json" -d '{}'

Khóa mới hiện một lần; khóa cũ vẫn ký tiếp 24 giờ, nên hãy triển khai khóa mới trong khoảng ấy — người nhận chấp nhận nhiều chữ ký nên không mất lần gửi nào. Khi một khóa bị lộ, gửi {"revoke_previous": true} để mọi khóa cũ ngừng ký ngay.

9. Tạm dừng và tiếp tục

curl -sS -X POST "$ORIGIN/api/v1/webhook-subscriptions/9b31…/pause" \
  -H "Authorization: Bearer $AGENTFLOW_KEY"

Trong lúc tạm dừng, sự kiện khớp vẫn được giữ lại (held_delivery_count tăng dần). .../resume thả chúng ra, cũ trước mới sau.

On this page