Receive event webhooks and verify their signatures

llms.txt

A webhook is SrvYard calling your code. When something happens in the organisation (a deployment finishes, a certificate fails to renew, a server disconnects) SrvYard sends a signed JSON request to an address you choose. You can post to your own release board, trigger a pipeline, feed a status page or keep an audit trail somewhere else.

This article shows how to add a webhook and choose its events, what a delivery looks like, how to verify that a request really came from SrvYard, what happens when your server fails, and how to read the delivery history and send a delivery again.

Before you begin

  • A plan with webhooks and a role that may manage them. Owners and admins can add and change webhooks; managers can view them. See Roles and permissions.
  • A public https address. SrvYard only calls https addresses. Addresses that carry a user name or password, use a blocked port or resolve to private or internal networks are refused, and redirects are not followed. For local testing use a tunnel that gives you a public https name.
  • Code that answers fast. Return a 2xx within 10 seconds and do the real work afterwards. SrvYard keeps only the first 2 KB of your answer.
  • Up to 20 webhooks per organisation.
Note

Webhooks send events for the whole organisation. If you only want a message in Slack, Discord, Telegram or email, use a notification channel instead. See Slack and Discord notifications.

1. Open the Webhooks page

Go to Organization settings > Webhooks. The organisation settings list is on the left of the page.

The Webhooks page of the organisation settings
Webhooks: one row per endpoint, with its status and last 24 hours.
  1. Add webhook. Opens the panel where you create one.
  2. Verify signatures. A reference panel with the headers, an example body and ready-made code for checking signatures (see step 4).
  3. Active. A webhook that is receiving events. The row also shows its name, address, the number of events it listens to (or All events), how many deliveries succeeded and failed in the Last 24 hours, the Last delivery and a hint of the signing secret (whsec_… and its last four characters).
  4. Switched off. A webhook that is not sending. It was switched off by hand, or SrvYard switched it off because it kept failing, in which case the reason is written under the badge.

2. Add a webhook

Press Add webhook.

The Add webhook panel with name, address and description
The Add webhook panel: a name, an https address and a description.
  1. Name. What you will call it in lists and alerts, for example Release tracker.
  2. Endpoint URL. The https address that receives the POST. The panel repeats the rules: https only, no private or internal addresses, no redirects, and the address is stored encrypted.
  3. Description (optional). Free text that appears under the name.
  4. Events. All events is on by default and includes events added in the future.

Switch All events off to choose.

The event picker with All events switched off
Switch All events off to choose individual events, grouped by area.
  1. All events is off; the picker appears.
  2. Search events. Filters the list by name or key.
  3. Group boxes. Each group (Servers, Sites, Deployments, Certificates and domains, Backups and snapshots, Monitoring, Databases, Recipes and imports, Team and access) has a box that ticks or unticks all its events, and a counter such as 8/8. Each event shows its label, its key (for example deployment.finished) and a coloured dot for its usual severity.

SrvYard offers 45 events today. Choose only the ones you handle: a webhook that listens to everything has to answer for everything. Press Add webhook to save.

The signing secret appears in a dialog titled Copy the signing secret, once.

Important

The secret (it starts with whsec_) is shown only now. Store it in the place your receiving code reads secrets from. If you lose it, use Rotate secret from the actions menu of the webhook to get a new one. The old secret keeps verifying for 24 hours after a rotation, so you can switch over without dropping a delivery.

3. Know what a delivery looks like

Every delivery is a POST with a JSON body and these headers:

Header Content
User-Agent SrvYard-Webhooks/1
X-SrvYard-Event The event key, for example deployment.finished.
X-SrvYard-Delivery The id of this delivery. A redelivery gets a new one.
X-SrvYard-Timestamp Unix seconds at which the request was signed.
X-SrvYard-Signature t=<timestamp>,v1=<hex hmac> (see below).

The body always has the same envelope:

{
  "id": "evt_4e0b9a1c-3c1f-4d9a-8d57-2b6f6a7c1e10",
  "type": "deployment.finished",
  "created_at": "2026-10-23T09:15:41+00:00",
  "organization": { "id": "2f7a1f64-8d3b-4b9e-a2c1-0e5d4c3b2a19", "handle": "acme" },
  "data": { "server": { }, "site": { }, "deployment": { } }
}

id is the id of the event: retries and redeliveries of the same event carry the same id, so use it to ignore duplicates. data depends on the event and holds ids (UUIDs), names, addresses, statuses and timestamps, with a link back to the dashboard. Secrets, passwords, tokens and .env values are never included. The same list of events, with an example data for each, is available from GET /api/v1/events (see API conventions).

4. Verify the signature

Anyone can post to your address, so check the signature before you trust a request. The Verify signatures panel documents it, with the headers and a full example body.

The Verify signatures panel
The Verify signatures panel documents the headers, the body and the check.
  1. Request. The headers listed above and the body.
  2. X-SrvYard-Signature. The signature is the HMAC-SHA256, in hex, of the text <timestamp>.<raw body> using the signing secret as key. While a rotated secret is still valid the header holds two v1= values: accept either.

Further down the panel has tabs for PHP, Node.js and curl / openssl. The check is always the same four steps:

  1. Read the raw body, before anything parses it into JSON.
  2. Split the header into t and the v1 values.
  3. Compute hmac_sha256(secret, t + "." + body) and compare it with each v1 in constant time.
  4. Reject the request if no value matches, or if t is more than 5 minutes away from your clock.

In Node.js:

import crypto from 'node:crypto';

const t = Number(/t=(\d+)/.exec(header)[1]);
const signatures = [...header.matchAll(/v1=([0-9a-f]{64})/g)].map((m) => m[1]);
const expected = crypto.createHmac('sha256', secret).update(`${t}.${rawBody}`).digest('hex');
const fresh = Math.abs(Date.now() / 1000 - t) <= 300;
const valid = fresh && signatures.some((s) => crypto.timingSafeEqual(Buffer.from(s), Buffer.from(expected)));

To test your code by hand, openssl gives the same value from a saved body:

echo -n "1700000000.$(cat body.json)" | openssl dgst -sha256 -hmac "$WEBHOOK_SECRET"
Tip

The common reason a correct secret fails is a body that was changed before hashing: re-encoded JSON, a trimmed newline, a framework that parsed it first. Hash the exact bytes you received.

5. Read deliveries and send again

Open the actions menu of a webhook (the three dots) and choose Deliveries. The menu also has Send test event, Edit, Rotate secret, Switch off (or Switch on) and Delete.

The deliveries of a webhook
Deliveries are kept for 30 days: status, HTTP code, duration and attempts.
  1. Send test event. Sends a test event to the address now, so you can see your code work without waiting for a real deployment.
  2. A failed delivery. The row shows the event, the status (Delivered or Failed), the HTTP code your server answered, how long it took and the Attempts used, here 3/8.
  3. Attempts. A delivery is tried up to 8 times in total.

Open a row to see exactly what was sent and what came back.

The detail of a failed delivery
One delivery: the request that was sent and what your server answered.
  1. Redeliver. Sends the same event again as a new delivery, after you have fixed your side. The panel also shows the error ("The endpoint answered 503."), the request headers, the request body and the first 2 KB of your response.

Deliveries are kept for 30 days, then removed.

6. What happens when your server fails

  • A success is any 2xx within 10 seconds. Redirects are not followed.
  • A timeout, a 5xx, 408 or 429 is retried after 1 minute, 5 minutes, 30 minutes, 2 hours, 6 hours, 12 hours and 24 hours: eight attempts in all. For 429 and 503, a Retry-After header that asks for longer wins.
  • Any other 4xx is not retried: it is a "no" from your code.
  • Repeated failure switches the webhook off. After 10 failed deliveries in a row, the first of them at least three days old, SrvYard switches the webhook off and tells the owners and admins by email and in the app. Fix the problem, choose Switch on from the actions menu, and the counters start from zero. Fixing it is easier if you answer 2xx immediately and queue the work.

Troubleshooting

  • The address is refused when I save. It is not https, it contains a user name or password, uses a blocked port, or resolves to a private network. Use a public https address.
  • My signature never matches. Hash the raw body, use the secret of this webhook (not another), use <timestamp>.<body> with the timestamp from the same header, and compare against every v1 value. After a rotation the header carries two.
  • I lost the secret. Use Rotate secret in the actions menu. Update your code within 24 hours; both secrets verify until then.
  • Everything shows Failed with 301 or 302. SrvYard does not follow redirects. Use the final address, with the right scheme and trailing slash.
  • Deliveries stopped and the status says Switched off. Too many failures in a row. Fix your endpoint and press Switch on.
  • I receive the same event twice. A retry after a slow answer or a redelivery. Both carry the same event id; store the ids you handled and skip repeats.
  • A test event works but real ones do not arrive. Check the webhook's events: with All events off, only the ticked ones are sent.
Last updated 2026-10-248 min readNeed help? Contact support