# Receiving & Verifying Webhooks

Source: https://docs.astalty.com.au/developers/webhooks/receiving-and-verifying-webhooks

> The webhook payload format, how to verify the signature, and how retries and delivery work.

> **This Feature is in Beta**
>
> Webhooks must be enabled for your account. Request access or send feedback through the support button or [Astalty Support](mailto:support@astalty.com.au).

## Your endpoint

Astalty sends an HTTP `POST` request with a JSON body when a subscribed event occurs. Your endpoint should:

- Accept `POST` requests with a JSON body.
- [Verify the signature](#verifying-the-signature) before trusting the request.
- Return a `2xx` status code, such as `200 OK`, within **15 seconds**.

Queue longer work and acknowledge the delivery first. A timeout is treated as a failed attempt.

Use the final public HTTPS address for your endpoint. Astalty does not follow redirects; a `3xx` response is a failed delivery attempt.

## The payload

Every webhook delivery has the same top-level shape:

```json
{
  "id": "9ee9dc30-c869-4fb5-9871-b32850e68cad",
  "event_type": "participant.created",
  "resource": {
    "id": "9ee9dc24-36e4-4e02-b734-32d982b83089",
    "type": "participant"
  },
  "data": []
}
```

| Field           | Description                                                                                                                                                                                                                                                          |
| --------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `id`            | The unique ID for this delivery. Retries of the same delivery keep this ID.                                                                                                                                                                                          |
| `event_type`    | The [event type](https://docs.astalty.com.au/developers/webhooks/supported-webhook-events), such as `task.created`.                                                                                                                                                  |
| `resource.id`   | The Astalty UUID of the related record.                                                                                                                                                                                                                              |
| `resource.type` | The related record type, such as `participant`.                                                                                                                                                                                                                      |
| `data`          | Event-specific data. Most events send an empty array. `job_application.created` sends an object with `screening_outcome`. Each event's page in the [Webhook Events reference](https://docs.astalty.com.au/developers/api-reference/webhook-events) shows its `data`. |

Webhook payloads identify what happened and which record it concerns. They do not embed the full record. If the public API offers a matching endpoint, use `resource.id` to retrieve the record's current state. For example, use `GET /public/v1/participants/{participant}` for a Participant event. Check the [API Reference](https://docs.astalty.com.au/developers/api-reference) for the matching endpoint.

For `job_application.created`, `data.screening_outcome` is `meets_all`, `missing_must_have`, `missing_nice_to_have` or `null`. This is the screening result when the application is created.

> **Handle duplicates**
>
> Your endpoint can receive the same delivery more than once. Store the delivery `id` after successful processing and make repeated processing safe.

## Verifying the signature

Every delivery includes this signature header:

```
X-Astalty-Signature: <signature>
```

The value is a lowercase hexadecimal **HMAC-SHA256** digest of the exact JSON request body, using the webhook's **Signing Secret** as the key. Read the raw body, calculate the digest and compare it with the header using a constant-time comparison.

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

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

// The signature is valid. The event can now be processed.
```

```javascript
// Node.js (Express) example
const crypto = require("crypto");

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

  return (
    expectedBytes.length === receivedBytes.length &&
    crypto.timingSafeEqual(expectedBytes, receivedBytes)
  );
}
```

> **Hash the raw body**
>
> Compute the HMAC over the exact bytes Astalty sent before parsing the JSON. Re-encoding the body can change the signed bytes and make a valid delivery fail verification.

## Retries and delivery

A delivery succeeds when the endpoint returns any `2xx` status within 15 seconds. Astalty retries when the endpoint cannot be reached, times out or returns a non-`2xx` status.

Astalty attempts each delivery **up to 10 times**:

- The initial attempt and **2 quick retries**, each with a 5-second delay.
- **7 scheduled attempts**, with delays of **1 minute**, **5 minutes**, **30 minutes**, **2 hours**, **5 hours**, **10 hours** and **24 hours**.

Queue processing can make an attempt run later than its scheduled delay. After all attempts fail, the delivery is marked **Failed**. From the webhook detail page, you can manually retry a **Failed** delivery or resend a **Delivered** one. See [Review Webhook Delivery Logs](https://docs.astalty.com.au/developers/webhooks/setting-up-a-webhook#review-delivery-logs).

Design the endpoint to process the same delivery `id` more than once without creating duplicate changes.
