# 2.4.0: Signed, timestamped webhooks, and the old header deprecated

2026-06-23 · https://galley.themes.dev/changelog/v2-4-webhook-signatures/

> Webhooks now carry a Runnel-Signature header with a timestamp, so captured requests can't be replayed. X-Runnel-Signature is deprecated and goes in 3.0.

## The Runnel-Signature header

Every webhook request now carries a `Runnel-Signature` header in the form `t=<unix time>,v1=<signature>`. The signature is an HMAC-SHA256 of the timestamp, a full stop and the raw body. Because the time is signed, you can reject requests older than five minutes, which stops anyone who captured a request from sending it again later.

The SDKs check both the signature and the age:

```ts
const event = runnel.webhooks.verify(rawBody, req.headers.get('runnel-signature'), process.env.RUNNEL_WEBHOOK_SECRET!);
```

## Secret rotation

You can now rotate an endpoint's secret without downtime. For 24 hours after a rotation, the header carries two `v1` signatures, one per secret, and the SDKs accept either.

## Deprecated: X-Runnel-Signature

The old `X-Runnel-Signature` header signed only the body, with no timestamp. We will keep sending it alongside the new one until Runnel 3.0, and then remove it. SDK versions before 2.4 read only the old header, so upgrade the SDK in your webhook handler first. See [Webhooks](/docs/reference/webhooks/) for the details.

## Fixes

- `run.retrying` was sent twice when an attempt timed out.
- The webhook log now shows the response body your endpoint returned, up to 2 KB.
