AnswerLine Sign in Start free

Webhooks

Add webhook: {"url": "https://…"} to an async task and we POST the result to that URL when the task finishes, so you don't have to poll.

Payload

The body is the JSON GET /v1/async/task/{id} returns for the finished task: task (with status COMPLETED or FAILED), credits, and response holding the engine's result or the error.

Test deliveries from the dashboard also carry "test": true at the top level; real deliveries never do. A test delivery is validly signed, so check this mark and ignore the delivery rather than storing its sample task.

Signatures

Every delivery carries a Webhook-Signature header, so you can check that it came from us and was not changed or replayed later:

Webhook-Signature: t=1757862000,v1=83be3d98e7e5b2c6e76161c2f7e25587e8fa570bfff030529a2f3aa3d756ae80
  • t is the time of sending in Unix seconds.
  • v1 is the hex HMAC-SHA256 of <t>.<raw body>, keyed with your account's signing secret (whsec_…). Owners find the secret on the Webhooks page of the dashboard.
  • While a rotated secret is phased out the header carries a second v1, one per secret. Accept the delivery if any v1 matches.

Deliveries also carry Webhook-Id, the delivery's id: the same on every retry of that delivery and not part of the signature. Quote it when you contact support about a delivery.

Verifying deliveries

Verify the raw body bytes before parsing: JSON that was parsed and serialized again will not match. Reject timestamps more than a few minutes from your clock (the SDKs default to 300 seconds) so a captured delivery cannot be replayed later.

TypeScript (Node 18+, Deno, Bun), with Express:

import express from "express";
import { verifyWebhook } from "@answerline/sdk";

const app = express();
app.post("/hooks/answers", express.raw({ type: "application/json" }), async (req, res) => {
  if (!(await verifyWebhook(req.body, req.get("webhook-signature"), process.env.WEBHOOK_SECRET!))) {
    return res.sendStatus(400);
  }
  const delivery = JSON.parse(req.body.toString("utf8"));
  // Store or queue the result, then answer quickly.
  res.sendStatus(204);
});

Python, with Flask:

import os
from flask import Flask, abort, request
from answerline import verify_webhook

app = Flask(__name__)

@app.post("/hooks/answers")
def answers():
    if not verify_webhook(request.get_data(), request.headers.get("Webhook-Signature"), os.environ["WEBHOOK_SECRET"]):
        abort(400)
    delivery = request.get_json()
    # Store or queue the result, then answer quickly.
    return "", 204

Any other language:

header     = request header "Webhook-Signature"
timestamp  = the value of t          (exactly one t)
signatures = every value of v1       (one or two)
reject unless |now - timestamp| <= 300 seconds
expected   = hex(HMAC_SHA256(key = secret, message = timestamp + "." + raw body))
accept if expected equals any signature, compared in constant time

Rotating the secret

Rotate the secret on the Webhooks page of the dashboard if it may have leaked. For the next 24 hours deliveries are signed with both the new and the previous secret, so switch your receiver to the new secret within that window and no delivery fails verification. The dashboard shows when the previous secret stops signing.

Retries and backoff

A delivery succeeds when your endpoint answers with a 2xx status within 15 seconds. Anything else, including a redirect, a timeout or a connection error, is retried: 5 seconds after the first failure, then doubling up to one hour between attempts, for up to 10 attempts. Webhook URLs must use https and resolve to public addresses; a URL that doesn't is not attempted again.

Order and duplicates

Deliveries are sent concurrently and retried independently, so they can arrive in any order, and the same delivery can arrive more than once (for example when your answer is lost, or after a redelivery). Deduplicate by task.id, and answer with a 2xx before doing slow work.

Delivery log and redelivery

The Webhooks page of the dashboard lists your account's deliveries, newest first, all of them or only the pending, delivered or dead ones. Each shows its Webhook-Id, the task, the host it goes to (never the rest of the URL, which often holds a secret), its attempts, the status your endpoint last answered, the last error, and when it was last attempted, delivered, or will be attempted next. A delivery is pending while it waits for its next attempt or is being sent, and dead once it is out of attempts or cannot be sent. Delivered and dead deliveries are kept for 30 days.

Owners can redeliver a delivered or dead delivery, for example once a broken endpoint is fixed: it is sent again at once with a fresh set of attempts, keeps its Webhook-Id, and is signed with the secrets valid when it is sent. A pending delivery cannot be redelivered, since it is retried on its own, and neither can one whose task is no longer kept. Redeliveries are limited per account per hour.

Testing your endpoint

Owners can send a sample completed task to any URL from the Webhooks page of the dashboard, signed like real deliveries and marked "test": true. The page shows the status your endpoint answered, how long it took and the start of its response body.