Webhooky
Přehled
BriefGate posílá na váš endpoint podepsané POST požadavky, kdykoli se u intake něco stane. Webhooky jsou nejlepší způsob, jak reagovat na aktivitu klienta v reálném čase — jsou efektivnější než dotazování get_intake_status ve smyčce a fungují i ve chvíli, kdy váš agent nic nedělá nebo spí mezi úkoly.
Každý požadavek je podepsaný, takže si ověříte, že přišel od BriefGate a nikdo s ním nemanipuloval.
Registrace endpointu
POST /v1/webhooks{
"url": "https://yourapp.com/webhooks/briefgate",
"events": ["item.submitted", "intake.completed"]
}Odebírat můžete libovolnou podmnožinu z pěti dostupných událostí. Pro odběr všech předejte všech pět názvů.
curl -X POST https://api.briefgate.dev/v1/webhooks \
-H "Authorization: Bearer bg_live_xxxxx" \
-H "Content-Type: application/json" \
-d '{"url":"https://yourapp.com/webhooks/briefgate","events":["item.submitted","intake.completed","intake.stalled"]}'Odpověď:
{
"id": "wh_01J3K...",
"url": "https://yourapp.com/webhooks/briefgate",
"events": ["item.submitted", "intake.completed", "intake.stalled"],
"secret": "whsec_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
"created_at": "2026-07-16T10:00:00Z"
}Hodnota secret se zobrazí právě jednou. Uložte si ji bezpečně — bez ní neověříte podpisy.
Ověření podpisu
Každý požadavek nese hlavičku:
X-BriefGate-Signature: t=1721131200,v1=abc123...t— Unixové časové razítko požadavkuv1— HMAC-SHA256(webhook_secret,${t}.${rawBody}) v hexu
Postup ověření:
- Z hlavičky vytáhněte
tav1. - Požadavek odmítněte, pokud
|teď - t| > 300 sekund(brání útoku přehráním). - Spočítejte
HMAC-SHA256(secret, "${t}.${rawBody}"), kderawBodyje surové tělo požadavku jako řetězec. - Porovnejte přes
timingSafeEqual— na porovnání podpisu nikdy nepoužívejte===.
Kompletní ověřovací funkce pro Node.js:
import { createHmac, timingSafeEqual } from 'crypto';
function verifyWebhook(rawBody, secret, signatureHeader) {
const parts = Object.fromEntries(
signatureHeader.split(',').map(p => p.split('=', 2))
);
const t = parts['t'];
const v1 = parts['v1'];
if (!t || !v1) throw new Error('Missing signature components');
if (Math.abs(Date.now() / 1000 - Number(t)) > 300) {
throw new Error('Timestamp out of tolerance window');
}
const expected = createHmac('sha256', secret)
.update(`${t}.${rawBody}`)
.digest('hex');
if (!timingSafeEqual(Buffer.from(v1), Buffer.from(expected))) {
throw new Error('Signature mismatch');
}
}verifyWebhook volejte dřív, než s obsahem cokoli uděláte. Když vyhodí výjimku, vraťte HTTP 400 a požadavek zahoďte.
Události
item.submitted
Spustí se, když klient v portálu odešle jednu položku.
{
"event": "item.submitted",
"intake_id": "int_01J3K...",
"item_key": "logo",
"item_type": "image",
"status": "submitted",
"submitted_at": "2026-07-16T14:22:00Z"
}intake.completed
Spustí se, jakmile jsou odeslané všechny povinné položky (nepovinné dokončení neblokují).
{
"event": "intake.completed",
"intake_id": "int_01J3K...",
"project_name": "Bella Cucina Website",
"client_email": "[email protected]",
"completed_at": "2026-07-18T09:11:00Z",
"items_count": 8
}client.viewed
Spustí se, když klient otevře svůj portál.
{
"event": "client.viewed",
"intake_id": "int_01J3K...",
"client_email": "[email protected]",
"viewed_at": "2026-07-16T13:55:00Z",
"ip": "a3f2c1d4..."
}Pole ip je jednosměrný hash — slouží jen k odstranění duplicit a k signálům o zneužití, zpětně se nedá rozšifrovat.
chase.bounced
Spustí se, když se upomínkový e-mail natvrdo odrazí nebo je nahlášen jako spam. BriefGate na tuhle adresu automaticky přestane posílat.
{
"event": "chase.bounced",
"intake_id": "int_01J3K...",
"chase_id": "ch_01J4M...",
"channel": "email",
"client_email": "[email protected]",
"bounced_at": "2026-07-18T08:00:00Z",
"reason": "hard_bounce"
}intake.stalled
Spustí se, když intake vyčerpá povolený počet upomínek — odešlo se jich max_reminders (výchozí 3) a klient stále nedokončil. Upomínkový engine zbývající upomínky zruší a intake předá zpátky vám.
{
"event": "intake.stalled",
"intake_id": "int_01J3K...",
"project_name": "Bella Cucina Website",
"client_email": "[email protected]",
"stalled_at": "2026-07-22T08:00:00Z",
"missing_items": ["logo", "hero_copy", "wp_admin"]
}Když dorazí intake.stalled, zvažte eskalaci přes SMS (send_chase s channel: "sms") nebo oslovte klienta jinou cestou.
Opakování pokusů
Když váš endpoint nevrátí HTTP 2xx, BriefGate to zkusí znovu s exponenciálně rostoucí prodlevou:
| Pokus | Odstup od předchozího |
|---|---|
| 1 (okamžitě) | — |
| 2 | 1 minuta |
| 3 | 5 minut |
| 4 | 25 minut |
| 5 | 2 hodiny |
| 6 | 12 hodin |
| 7 | 12 hodin |
Po sedmi neúspěšných pokusech se doručení vzdá a v logu se označí jako failed.
Vrácením HTTP 410 Gone endpoint natrvalo odregistrujete a další doručování na tuto adresu se zastaví.
Historie doručení:
GET /v1/webhooks/:id/deliveriesTestování
Na endpoint můžete poslat umělý testovací obsah, aniž byste zakládali skutečný intake:
POST /v1/webhooks/:id/test{
"event": "intake.completed"
}BriefGate odešle realistický, ale vymyšlený obsah. Použijte to k ověření, že je váš handler dostupný a že ověřování podpisu funguje, ještě než půjdete naostro.
Doporučení
Nejdřív ověřte podpis. Nezpracovávejte obsah dřív, než zavoláte ověřovací funkci. Požadavek bez platného podpisu okamžitě zahoďte.
Počítejte s idempotencí. Webhook může ve výjimečných případech dorazit vícekrát (opakování kvůli síti, restart infrastruktury). Kontrolujte kombinaci intake_id + event, ať tutéž událost nezpracujete dvakrát.
Surové tělo zalogujte před parsováním. Když váš JSON parser spadne, budete ho chtít mít k dispozici pro ladění. Uložte ho ještě před JSON.parse.
Odpovídejte rychle. Na vrácení odpovědi 2xx má endpoint 30 sekund. Pokud handler dělá něco náročného (volá externí API, sestavuje soubory), potvrďte příjem hned a zbytek odbavte na pozadí.
Nespoléhejte na pořadí doručení. Události item.submitted mohou dorazit před intake.completed i po ní. Handler navrhněte tak, aby zvládl události v libovolném pořadí.