Webhooks
Billing and task push events signed with HMAC-SHA256.
Webhooks
Webhooks push billing and task events to you. Register an HTTPS endpoint, subscribe to event types, and receive signed HMAC-SHA256 POST requests.
Quick start
# register an endpoint (the secret is shown exactly once!)
curl -X POST -H "X-API-Key: $FRANKLAB_KEY" -H "Content-Type: application/json" \
-d '{"url":"https://example.com/franklab-hook","eventTypes":["billing.settled","task.succeeded"]}' \
https://apergrex.ru/franklab/api/v1/webhooks/endpoints
# test delivery
curl -X POST -H "X-API-Key: $FRANKLAB_KEY" \
https://apergrex.ru/franklab/api/v1/webhooks/endpoints/<id>/test
Event types
| Event | When |
|---|---|
billing.settled | a charge was confirmed (with the final amount) |
billing.refunded | a task reservation was refunded |
billing.topup.confirmed | a top-up was credited |
billing.low_balance | balance dropped below 1,000 ₣ (at most once per 24h) |
billing.invoice.status_changed | a legal invoice changed status / was paid |
task.succeeded / task.failed / task.refunded | a generation task reached a terminal status |
webhook.test | a test event (on demand) |
Delivery format
Headers:
X-Franklab-Event: billing.settled
X-Franklab-Event-Id: <event uuid — your idempotency key>
X-Franklab-Timestamp: <unix seconds>
X-Franklab-Signature: sha256=<HMAC-SHA256(secret, "{timestamp}.{body}")>
Body — one envelope:
{
"eventId": "…",
"eventType": "billing.settled",
"occurredAt": "2026-09-04T12:00:00.000Z",
"partnerId": "…",
"schemaVersion": 1,
"data": { "transactionId": "…", "amountFranks": 84 }
}
Verifying the signature
const expected = crypto.createHmac('sha256', secret)
.update(`${timestamp}.${rawBody}`)
.digest('hex');
// compare against X-Franklab-Signature (strip the sha256= prefix) and reject
// timestamps older than ~5 minutes (anti-replay)
Delivery guarantees
- At-least-once: duplicates are possible — deduplicate by
eventId. - Duplicates can also appear after a poller restart — the robust dedupe key is
eventType+data.transactionId/data.taskId/data.invoiceId. - Latency is up to a minute (poller cadence).
- Retries on failure: 1 min → 5 min → 30 min → 2h → 6h, then dead-letter (visible via
GET /v1/webhooks/deliveries?status=dead_lettered). - The
uncertainstatus means the request left but the outcome is unknown: those deliveries are never auto-retried — replay them viaPOST /v1/webhooks/deliveries/{id}/replay. - Answer 2xx quickly; the timeout is 10 seconds.
Management
| Operation | Endpoint |
|---|---|
| List | GET /v1/webhooks/endpoints |
| Register | POST /v1/webhooks/endpoints |
| Delete | DELETE /v1/webhooks/endpoints/{id} |
| Rotate secret | POST /v1/webhooks/endpoints/{id}/rotate-secret |
| Test | POST /v1/webhooks/endpoints/{id}/test |
| Delivery history | GET /v1/webhooks/deliveries?endpointId=…&status=… |
Limits: up to 10 endpoints per partner; the URL must be public HTTPS (internal hosts and IP literals are rejected); redirects are never followed.
For AI agents/en/docs/webhooks.md