Command Palette

Search for a command to run...

Log In
  1. Reference
  2. Webhooks
  3. Receiving & Verifying Webhooks

Receiving & Verifying Webhooks

Your endpoint

When a subscribed event happens, Astalty sends an HTTP POST request to your webhook URL with a JSON body. Your endpoint should:

  • Accept POST requests with a JSON body.
  • Verify the signature before trusting the request.
  • Respond quickly with a 2xx status code (for example 200 OK) to acknowledge receipt. Astalty waits up to 15 seconds for a response.

Do the minimum work needed to acknowledge the event, then process it asynchronously. If you do slow work before responding, you risk hitting the timeout, which Astalty treats as a failed delivery.

The payload

Every webhook delivery has the same top-level shape:

{
  "id": "9ee9dc30-c869-4fb5-9871-b32850e68cad",
  "event_type": "participant.created",
  "resource": {
    "id": "9ee9dc24-36e4-4e02-b734-32d982b83089",
    "type": "participant"
  },
  "data": {}
}
FieldDescription
idA unique ID for this delivery. Useful for logging and for ignoring duplicates.
event_typeWhich event occurred, e.g. task.created.
resource.idThe ID of the record the event relates to.
resource.typeThe type of that record, e.g. participant.
dataAn optional payload that Astalty may include with the event.

Webhook payloads are intentionally lightweight — they tell you what happened and to which record, rather than embedding the full record. To get the current details, call the matching API endpoint using resource.id (for example, GET /public/v1/participants/{id}). This keeps payloads small and ensures you always read the latest state.

Verifying the signature

So you can be sure a request really came from Astalty, every delivery includes a signature header:

X-Astalty-Signature: <signature>

The signature is an HMAC-SHA256 hash of the exact JSON request body, using your webhook's signing secret (shown on the webhook's detail page in the API Dashboard) as the key. To verify a request, compute the same hash over the raw request body and check it matches the header — using a constant-time comparison.

// PHP example
$payload = file_get_contents('php://input');           // the raw request body
$signature = $_SERVER['HTTP_X_ASTALTY_SIGNATURE'] ?? '';
$expected = hash_hmac('sha256', $payload, $signingSecret);

if (! hash_equals($expected, $signature)) {
    http_response_code(403);
    exit;
}

// Signature is valid — safe to process the event.
// Node.js (Express) example
const crypto = require("crypto");

function verify(rawBody, signatureHeader, signingSecret) {
  const expected = crypto
    .createHmac("sha256", signingSecret)
    .update(rawBody)
    .digest("hex");
  return crypto.timingSafeEqual(
    Buffer.from(expected),
    Buffer.from(signatureHeader || "")
  );
}

Retries and delivery

A delivery is considered successful when your endpoint returns any 2xx status within the 15-second timeout. If it returns a different status, can't be reached, or times out, Astalty automatically retries.

Astalty attempts each delivery up to 10 times:

  • 3 quick attempts, roughly 5 seconds apart.
  • Then 7 further attempts on an increasing back-off schedule: after 1 minute, 5 minutes, 30 minutes, 2 hours, 5 hours, 10 hours, and finally 24 hours.

If every attempt fails, the delivery is marked failed. You can inspect the attempts and retry a failed delivery manually from the webhook's detail page — see Setting up a webhook.

Because retries are expected, your endpoint should be idempotent — processing the same delivery id twice should be safe.