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
POSTrequests with a JSON body. - Verify the signature before trusting the request.
- Respond quickly with a
2xxstatus code (for example200 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": {}
}
| Field | Description |
|---|---|
id | A unique ID for this delivery. Useful for logging and for ignoring duplicates. |
event_type | Which event occurred, e.g. task.created. |
resource.id | The ID of the record the event relates to. |
resource.type | The type of that record, e.g. participant. |
data | An 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.