Webhook — hodisalarni qabul qilish

Platformada nimadir sodir bo'lganda sizga HTTPS POST keladi. Xuddi shu mexanizm agentni ishga tushirish uchun ham ishlatiladi (run.execute).


1. Obuna bo'lish


curl -X POST $AFAI_BASE_URL/api/v1/webhooks \
  -H "Authorization: Bearer $ACCESS_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "url": "https://acme.uz/afai/hooks",
    "events": ["run.succeeded", "run.failed"],
    "description": "Hisobot uchun"
  }'

Javobda secret bo'ladi (whsec_…) — u faqat shu yerda ko'rsatiladi. Imzoni tekshirish uchun kerak.

Maskalar qo'llanadi: "run.*" — barcha run hodisalari, "*" — hammasi.


2. Hodisalar

HodisaQachon
run.createdIsh yaratildi
run.awaiting_approvalXavfli tool tasdiq kutmoqda
run.resumedTasdiqdan keyin davom etdi
run.succeededMuvaffaqiyatli tugadi
run.failedXato bilan tugadi
run.cancelledBekor qilindi
notification.createdAgent bildirishnoma chiqardi
webhook.disabledObuna avtomatik o'chirildi
webhook.testSinov tugmasi bosildi
custom.*emit_event tool'i orqali agent chiqargan

Payload:


{
  "id": "evt_…",
  "type": "run.succeeded",
  "created_at": "2026-08-14T12:33:36.260536+00:00",
  "workspace_id": "ws_…",
  "data": { "run_id": "run_…", "app_id": "app_…", "duration_ms": 134 }
}

3. Imzo — majburiy tekshiruv

Har bir so'rovda:


X-AFAI-Signature: t=1786710418,v1=5f0b…c3
X-AFAI-Event: run.succeeded
X-AFAI-Delivery: whd_…
X-AFAI-Attempt: 1

v1 = HMAC-SHA256(secret, "{t}.{xom_tana}").

SDK bilan:


from afai_sdk import verify_signature, InvalidSignature

try:
    verify_signature(secret, raw_body, request.headers["X-AFAI-Signature"])
except InvalidSignature as exc:
    return 401, str(exc)

import { verifySignature } from "./afai.js";
verifySignature(secret, rawBody, req.headers["x-afai-signature"]);

Qo'lda:


import hashlib, hmac, time

def verify(secret: str, body: bytes, header: str, tolerance: int = 300) -> bool:
    parts = dict(p.split("=", 1) for p in header.split(","))
    timestamp = int(parts["t"])
    if abs(time.time() - timestamp) > tolerance:
        return False                      # replay
    expected = hmac.new(secret.encode(), f"{timestamp}.".encode() + body,
                        hashlib.sha256).hexdigest()
    return hmac.compare_digest(expected, parts["v1"])

Uchta tez-tez uchraydigan xato:

1. Parsing qilingan tanani imzolash. Imzo xom baytlar bo'yicha. Express'da express.json() emas, express.raw() ishlating. 2. t ni tekshirmaslik. Bo'lmasa eski so'rovni qayta yuborish mumkin. 3. == bilan solishtirish. compare_digest / timingSafeEqual ishlating.


4. Javob va qayta urinish

2xx qaytaring — tana muhim emas. Uzoq ish qilmang: 10 soniya timeout, navbatga qo'yib darhol javob bering.

Muvaffaqiyatsiz bo'lsa jadval bo'yicha qayta urinamiz:


10s → 1m → 5m → 30m → 2s → 6s      (jami 6 urinish)

Ketma-ket 5 marta muvaffaqiyatsiz bo'lsa obuna o'chiriladi:

  • status: "disabled" bo'ladi va last_error yoziladi;
  • tashkilotda webhook.disabled hodisasi paydo bo'ladi;
  • audit jurnaliga yoziladi.
  • Tuzatgandan keyin:

    
    curl -X POST $AFAI_BASE_URL/api/v1/webhooks/$ID/enable \
      -H "Authorization: Bearer $ACCESS_TOKEN"
    
    «Jim o'lgan» webhook eng yomon holat — shuning uchun o'chirish jarayoni
    ko'rinadigan qilingan.

    5. Debug

    Sinov yuborish — «menda ishlamayapti» savolining 80% shu bilan yopiladi:

    
    curl -X POST $AFAI_BASE_URL/api/v1/webhooks/$ID/test \
      -H "Authorization: Bearer $ACCESS_TOKEN"
    

    Javobda delivered, response_code, error va (xato bo'lsa) hint bo'ladi.

    Tarix:

    
    curl $AFAI_BASE_URL/api/v1/webhooks/$ID/deliveries \
      -H "Authorization: Bearer $ACCESS_TOKEN"
    

    Webhook o'rnatolmasangiz (lokal mashina, korporativ tarmoq) — polling:

    
    curl "$AFAI_BASE_URL/api/v1/events?since=evt_…" \
      -H "Authorization: Bearer $ACCESS_TOKEN"
    

    6. Idempotentlik

    Bitta hodisa bir necha marta yetib borishi mumkin (masalan javob kechikkan, biz qayta urinib ko'rgan). Shuning uchun X-AFAI-Delivery yoki event.id bo'yicha takrorlanishni o'zingiz filtrlang:

    
    if already_processed(event["id"]):
        return 200
    

    7. Xavfsizlik

  • Manzil ochiq HTTPS bo'lishi kerak — ichki IP va localhost taqiqlangan
  • (SSRF himoyasi, docs/SECURITY.md §4).

  • Sir yo'qolsa — obunani o'chirib yangisini yarating.
  • Imzo tekshirilmagan handler — integratsiyangizdagi eng katta teshik:
  • har kim sizga soxta «to'lov o'tdi» yubora oladi.