Guide to Ashby Webhooks: Features and Best Practices
Ashby webhooks notify your systems about recruiting activity: an application is submitted, a candidate is hired, an interview is scheduled. If you're building integrations on Ashby's ATS, webhooks are how you react to hiring events without polling.
This guide covers how Ashby webhooks work, the events you'll handle, how to verify the Ashby-Signature, the ping event and auto-disable behavior, and the best practices for production.
What are Ashby webhooks?
Ashby webhooks are HTTP POSTs delivered to a URL you configure per webhook, each carrying an { action, data } envelope where action is the event name. Ashby signs every delivery with an Ashby-Signature header: sha256= followed by an HMAC-SHA256 of the raw request body, keyed with that webhook's secret.
Ashby webhook features
| Feature | Details |
|---|---|
| Configuration | Admin > Integrations > Webhooks (one URL + one event type + one secret per webhook) |
| Signature header | Ashby-Signature, formatted sha256=<hex> |
| Signature scheme | HMAC-SHA256 of the raw body, hex, keyed with the per-webhook secret |
| Secret | Optional but strongly encouraged, per-webhook |
| Payload | { "action": "<eventName>", "data": {...} } |
| Ping | A ping event is sent on create/edit |
| Auto-disable | Webhook disabled if your endpoint returns >= 400 |
| Retries | Not documented (only the disable behavior is) |
| SDK | None |
Common events
Ashby event names are camelCase (no dot notation), and the action field equals the event name:
| Event | Fires when |
|---|---|
applicationSubmit | An application is submitted |
applicationUpdate | An application updates |
candidateHire | A candidate is hired |
candidateStageChange | A candidate moves stage |
interviewScheduleCreate | An interview is scheduled |
Some events fan out: candidateHire, for example, can also fire applicationUpdate and candidateStageChange. Note it's interviewScheduleCreate, not interviewSchedule.create. Each webhook is scoped to one event type; consult Ashby's webhook reference for the full camelCase catalog.
Setting up Ashby webhooks
Create webhooks under Admin > Integrations > Webhooks: one endpoint URL, one event type, and one secret token per webhook. The secret is optional but strongly encouraged. On create or edit, Ashby sends a ping event so you can confirm the endpoint works. Requests also carry an Ashby-Webhook user-agent header, which is informational, don't use it for authentication.
Securing Ashby webhooks
Each delivery carries an Ashby-Signature header formatted sha256=<hex>, where the digest is an HMAC-SHA256 of the raw request body keyed with the per-webhook secret. Compute over the exact raw bytes, then compare the hex after the sha256= prefix in constant time.
const crypto = require("crypto");
const SECRET = process.env.ASHBY_WEBHOOK_SECRET;
function verify(rawBody, signatureHeader) {
const expected = "sha256=" +
crypto.createHmac("sha256", SECRET).update(rawBody).digest("hex");
const a = Buffer.from(expected);
const b = Buffer.from(signatureHeader || "");
return a.length === b.length && crypto.timingSafeEqual(a, b);
}
app.post("/webhook", express.raw({ type: "application/json" }), (req, res) => {
if (!verify(req.body, req.headers["ashby-signature"])) {
return res.sendStatus(401);
}
const { action, data } = JSON.parse(req.body);
res.sendStatus(200); // acknowledge fast (>= 400 disables the webhook)
if (action !== "ping") processQueue.add({ action, data }); // process async
});
The same check in Python:
import hashlib
import hmac
import os
SECRET = os.environ["ASHBY_WEBHOOK_SECRET"].encode()
def verify(raw_body: bytes, signature_header: str) -> bool:
expected = "sha256=" + hmac.new(SECRET, raw_body, hashlib.sha256).hexdigest()
return hmac.compare_digest(expected, signature_header or "")
Ashby webhook limitations and pain points
Returning >= 400 disables the webhook
The Problem: If your endpoint returns any status >= 400, Ashby auto-disables the webhook until you manually re-enable it. A transient bug that 500s, or a strict 401 on a delivery you meant to accept, can silently switch off the integration.
Why It Happens: Ashby treats a 4xx/5xx as a broken endpoint and disables to stop delivering.
Workarounds:
- Acknowledge with a 200 once the signature verifies, and handle processing errors internally (queue and retry) rather than returning an error status.
- Monitor webhook status and re-enable after fixing issues.
How Hookdeck Can Help: Hookdeck always returns success to Ashby and absorbs downstream failures itself, so a transient error never disables your webhook; it retries to your service on its own schedule.
Undocumented retries
The Problem: Ashby documents the disable behavior but not a retry schedule, so you can't assume a failed delivery will be retried.
Why It Happens: The retry/backoff policy isn't published.
Workarounds:
- Persist events on receipt and reconcile via the API for anything you can't afford to miss.
How Hookdeck Can Help: Hookdeck durably stores and retries deliveries on a schedule you control, giving you retry guarantees Ashby doesn't document.
Event fan-out and the ping
The Problem: Some actions fan out into multiple events (candidateHire also fires applicationUpdate and candidateStageChange), and a ping arrives on create/edit. Handlers that don't account for both over- or mis-process.
Why It Happens: Ashby emits related events together and pings to validate endpoints.
Workarounds:
- Branch on
action, ignorepingfor business logic, and make handlers idempotent so overlapping events don't double-count.
How Hookdeck Can Help: Hookdeck's filters route by action, so you can send only the events you care about to each handler.
One event type per webhook
The Problem: Each webhook is scoped to a single event type, so covering many events means managing many webhooks (and secrets).
Why It Happens: Ashby scopes webhooks narrowly.
Workarounds:
- Script webhook creation, or centralize downstream.
How Hookdeck Can Help: Hookdeck consolidates multiple Ashby webhooks into one ingestion point with routing.
Best practices
Verify sha256= over the raw body
Compute sha256= + hex HMAC-SHA256 over the raw body with the per-webhook secret and compare in constant time.
Never return >= 400 for a delivery you accepted
Return 200 once verified and handle processing failures internally, so a transient error doesn't disable the webhook. See why to process webhooks asynchronously.
Handle ping and fan-out, dedupe
Ignore ping for business logic, branch on action, and make handlers idempotent for fanned-out events.
Persist and reconcile
Since retries aren't documented, log events and reconcile via the API for anything critical.
Make Ashby webhooks production-ready
Hookdeck verifies Ashby-Signature, keeps your webhook from disabling, deduplicates, and durably queues every hiring event
Conclusion
Ashby webhooks deliver camelCase hiring events in an { action, data } envelope, verified with an Ashby-Signature (sha256= + hex HMAC over the raw body). The sharp edge is that returning >= 400 disables the webhook, so acknowledge fast and handle failures internally, account for the ping and event fan-out, and persist events since retries aren't documented.
Hookdeck verifies the signature, keeps your webhook alive by absorbing downstream failures, deduplicates, and durably queues every event at the edge, so your app processes verified, unique hiring events.
Get started with Hookdeck for free and handle Ashby webhooks reliably in minutes.