Webhooks and Callbacks
An event-driven asynchronous structure used so your system is notified immediately when long-running model operations, pipeline runs, or agent actions requiring human approval complete.
01Why Are Asynchronous Requests Needed?
Complex models such as Birk-Agent-Heavy may run deep data analysis, execute long processes, or coordinate multiple agents, so response times can increase. Instead of limiting your system with HTTP timeouts or making the client wait, you can use webhook architecture.
With the webhook approach, your application starts the job, Birk continues execution in the background, and important status changes are sent to the endpoint you define. The UI does not freeze, the result is not lost if a mobile app moves to the background, and long-running enterprise flows become traceable.
When you provide a webhook_url while starting a request, the result is automatically POSTed to the URL you specify when the job finishes or an intermediate status changes.
02Delivery Flow
Start Job
The API request is sent with webhook_url and optional metadata.
Accept
Birk queues the job and creates a job_id.
Run
The model, agent, or pipeline progresses in the background.
Deliver
The result or intermediate event is sent to your webhook endpoint.
03Event Catalog
A webhook is not only a 'job finished' notification. In long-running flows, queued, started, progress, success, failure, and human approval states should be tracked as separate event types.
job.queued
Sent when a long-running job is queued.
job.started
Sent when model or pipeline execution actually starts.
job.progress
Sent when a meaningful intermediate stage completes inside a multi-step pipeline.
job.completed
Sent when the job completes successfully.
job.failed
Sent when the job cannot complete because of a permanent error.
approval.required
Sent when an agent needs human approval for a critical action.
04Example Webhook Payload
When the Birk model finishes processing, it sends a JSON structure like the one below to your server. Your server is expected to accept this request and quickly return HTTP 2xx. If long processing is required, enqueue the payload internally and do not keep the webhook request waiting.
{
"id": "evt_01J2QZ8R7K9M",
"event_type": "job.completed",
"created": 1767326500,
"delivery_attempt": 1,
"job": {
"id": "job_123abc",
"status": "success",
"model": "birk-agent-heavy-v1",
"started_at": "2026-05-02T12:41:04Z",
"completed_at": "2026-05-02T12:42:18Z"
},
"data": {
"result": {
"role": "assistant",
"content": "Analysis completed. The summary you expected..."
},
"usage": {
"prompt_tokens": 1840,
"completion_tokens": 612,
"total_tokens": 2452
}
},
"metadata": {
"customer_id": "cus_7842",
"workflow": "monthly_sales_review"
}
}HTTP method
Birk webhook deliveries are sent to the receiver endpoint as POST requests.
Success response
If the receiver endpoint returns 2xx, the delivery is accepted as successful.
Timeout
If the receiver system does not respond within a reasonable time, the delivery fails and enters the retry plan.
Ordering
Events for the same job are attempted in logical order; the receiver side should still be designed as idempotent.
Redelivery
The same event can be sent again on network errors or 5xx statuses.
05Signature Verification and Security
What you need to verify
The x-briq-signature header must match the payload.
If x-briq-timestamp is too old, the request should be rejected.
The endpoint should only run over HTTPS.
The same event_id should not be processed twice.
import crypto from "node:crypto";
function verifyBriqSignature({ rawBody, timestamp, signature, secret }) {
const signedPayload = `${timestamp}.${rawBody}`;
const expected = crypto
.createHmac("sha256", secret)
.update(signedPayload)
.digest("hex");
return crypto.timingSafeEqual(
Buffer.from(signature),
Buffer.from(expected)
);
}06Retry and Idempotency
Webhook delivery can be affected by network conditions. Your receiver must therefore be designed to receive the same event more than once. The critical rule is: the event may arrive again, but business logic must not run again.
Retry
Responses outside 2xx and timeouts can cause redelivery.
Idempotency
event_id or job.id + event_type should be stored as a unique key.
Delayed delivery
Early attempts use short intervals, later attempts use increasing wait time.
async function handleWebhook(event) {
const alreadyProcessed = await db.webhookEvents.findUnique({
where: { eventId: event.id }
});
if (alreadyProcessed) {
return { status: 200, body: "already processed" };
}
await db.webhookEvents.create({
data: {
eventId: event.id,
type: event.event_type,
jobId: event.job.id
}
});
await enqueueBusinessWork(event);
return { status: 200, body: "accepted" };
}07Receiver Endpoint Example
export async function POST(request) {
const rawBody = await request.text();
const signature = request.headers.get("x-briq-signature");
const timestamp = request.headers.get("x-briq-timestamp");
const valid = verifyBriqSignature({
rawBody,
signature,
timestamp,
secret: process.env.BRIQ_WEBHOOK_SECRET
});
if (!valid) {
return new Response("invalid signature", { status: 401 });
}
const event = JSON.parse(rawBody);
await handleWebhook(event);
return new Response("ok", { status: 200 });
}Endpoint behavior
08Production Checklist
Your webhook endpoint should be public, but signature verification must be mandatory.
If the same event_id arrives a second time, the operation must not run again.
If business logic takes a long time, do not run it inside the webhook request; enqueue it and return a fast 2xx.
Track payload size, timeout, and error rate as separate metrics.
Rotate old signature keys on a plan and accept both keys during the transition period.
For critical events, produce the user-facing message in your product language instead of showing the raw error.
Secret key
The webhook secret should be stored in environment variables and rotated regularly.
Event record
Each event delivery should be recorded with status, attempt count, and error message.
Alarm
If failed deliveries exceed a defined rate, the operations team should be alerted.