Skip to main content

Webhooks

Signed event delivery with retries, logs, and redelivery.

Written by Traian

Webhooks push events from your board to your own systems the moment they happen: a new application arrives, a job goes live, a candidate signs up. Instead of polling the API, you give easyboard a URL and it POSTs a signed JSON payload to it for every event you subscribe to. Deliveries are retried on failure and logged so you can see exactly what was sent and what your server answered.

To manage webhooks: in the sidebar, open Distribution and click Webhooks.

The Webhooks page with the endpoints card, the recent deliveries log, and the Zapier card

Add an endpoint

  1. In the sidebar, open Distribution and click Webhooks.

  2. In the Webhook endpoints card, click Add endpoint.

  3. Enter a Name that identifies the receiver (for example "ATS sync" or "Internal Slack relay").

  4. Pick a Destination: Generic (signed JSON) for your own server or tools like Zapier, or Discord / Slack to post formatted messages straight into a channel (covered in Connect Slack, Discord, and Zapier).

  5. Enter the URL easyboard should POST to. It must be a public HTTPS endpoint; private or internal addresses are rejected for security.

  6. Under Events, check the events you want. Leave all boxes unchecked to subscribe to everything, including new event types easyboard adds later.

  7. Click Add endpoint.

A dialog titled Signing secret appears once. Click Copy and store the secret in your receiver's configuration, then click Done. You will not be able to see it again (you can rotate it later to get a new one). The endpoint now appears in the list with its URL and the number of subscribed events, or "All events".

The events you can subscribe to

  • job.created: an admin (or aggregator) added a new job posting.

  • job.published: a draft job became publicly visible on the board.

  • job.expired: a job's expiry date passed or an admin closed it.

  • application.received: a job seeker applied to one of your jobs.

  • candidate.created: a new candidate profile was added to the workspace.

  • candidate.updated: an admin edited a candidate's profile.

  • employer.created: a new employer account was added to the workspace.

What a delivery looks like

Generic endpoints receive a POST with a JSON envelope:

{  "event": "job.published",  "id": "a-unique-event-id",  "created_at": "2026-07-22T09:30:00.000Z",  "data": { "id": "...", "slug": "...", "title": "...", "company_name": "...", "status": "published", "location": "...", "remote": false }}

Each delivery carries these headers:

  • X-Easyboard-Event: the event type, for quick routing.

  • X-Easyboard-Delivery: the unique event id. Store it and skip duplicates you have already processed, since retries can deliver the same event twice.

  • X-Easyboard-Timestamp: Unix time of the send.

  • X-Easyboard-Signature: the signature to verify (next section).

Respond with any 2xx status quickly; do your heavy processing after acknowledging.

Verify the signature

Anyone who discovers your endpoint URL could POST fake events to it, so verify every delivery. The X-Easyboard-Signature header has the form sha256=<hex>, where the hex value is an HMAC-SHA256 of the raw request body, keyed with your endpoint's signing secret. In your receiver:

expected = hex( HMAC_SHA256( signing_secret, raw_request_body ) )valid    = ( "sha256=" + expected ) equals the X-Easyboard-Signature header

Compute the HMAC over the exact raw bytes of the body, before any JSON parsing, and use a constant-time comparison if your language offers one. Reject anything that does not match. Discord and Slack destinations are not signed; the signature applies to Generic endpoints only.

Send a test event

  1. In the endpoint's row, click the paper-plane icon (Send test event).

  2. You will see "Test event queued. Check the deliveries log below".

  3. In the Recent deliveries card, a webhook.test delivery appears within a few seconds with its result.

The test goes through the exact same signed delivery pipeline as real events, so it is the right way to confirm your signature verification works before going live.

Retries and auto-disable

Delivery follows strict, predictable rules:

  • Each attempt has a 10-second timeout, and redirects are not followed (a 3xx response counts as a failure and is not retried, so point easyboard at the final URL).

  • A 2xx response is a success.

  • A 4xx response (except 408 and 429) is treated as a permanent error and is not retried.

  • 5xx responses, 408, 429, timeouts and network errors are retried with increasing backoff, up to 6 attempts in total.

  • After 10 consecutive failed deliveries, the endpoint is automatically disabled and shows an Auto-disabled badge. Any successful delivery resets the failure counter.

To bring an auto-disabled endpoint back: fix your receiver, then click the pencil icon, make sure Active is on, and click Save changes. Saving with Active on clears the disabled state and resets the failure counter.

The deliveries log and redelivery

The Recent deliveries card shows the last 20 delivery attempts across all your endpoints: the event type, a Succeeded / Failed / Pending badge, the HTTP status your server returned, the endpoint name, when it happened, and the attempt count. Click the circular-arrow icon on any row to re-queue that delivery; it goes through the full pipeline again with the same payload. This is the fastest recovery after an outage on your side: fix the receiver, then redeliver the failed rows.

Rotate, pause, or delete an endpoint

  • Rotate the signing secret: click the circular-arrow icon on the endpoint row and confirm. The new secret is shown once; the old one stops validating immediately, so update your receiver right away.

  • Pause: click the pencil icon, turn Active off, and save. The endpoint shows a Paused badge and receives nothing, but keeps its configuration and secret.

  • Delete: click the trash icon and confirm with Delete endpoint. The endpoint stops receiving events immediately and this cannot be undone.

Webhooks tell you that something happened; to fetch full details or make changes, pair them with the REST API.

Did this answer your question?