Event Versioning
When an event type's payload has to change incompatibly, add a version instead of breaking every receiver at once. You always send the newest shape. Each subscription is pinned to a version, and Hookbase converts the payload down to the pinned version before it signs and delivers it. Receivers move to the new version when they're ready. Stripe and Shopify version their webhooks the same way.
Event versioning is available on Starter and above. If you never add a version, nothing changes: an event type without versions works exactly as it always has.
How it works
- An event type has an ordered list of versions, oldest to newest. Each version has its own JSON Schema and example payload, an optional description, and can be deprecated with a sunset date.
- Every version except the oldest can have a downgrade expression. It's JSONata that turns a payload of that version into the previous version. Leave it empty when the change is additive (only new fields), because older receivers ignore fields they don't know.
- Every subscription either follows
latestor is pinned to a version. - When you send an event, Hookbase walks the chain from the version you sent down to each subscription's version. For example, sending
2026-10-01to a subscription pinned tov1runs2026-10-01 → v2, thenv2 → v1. If the subscription has a transform, it runs after the conversion. - Conversion only goes down. A subscription pinned to a newer version than the one you sent, or following
latest, receives the payload as you sent it.
Each delivery carries the version its body is in:
x-hookbase-event-version: v1A custom endpoint header can't override it. Event types without versions don't send it. The signature covers the body, so a receiver can trust the shape it verified, and the header tells it which shape that is.
Adding versions
Open Outbound → Event Types, click the Versions button on an event type, and choose Add baseline version.
- The baseline (first) version records the shape you send today. Its schema and example default to the event type's current ones. Every existing subscription is pinned to it, so adding the next version changes nothing for current receivers.
- Each later version becomes the latest. Give it the new schema, a new example, and a downgrade expression that turns the new shape back into the previous one. Before you save, click Test downgrade on example: Hookbase runs the expression on the example payload and checks the output against the previous version's schema.
The event type's own schema and example always mirror the latest version, so the public catalog, the portal and payload validation show the newest shape.
Downgrade expressions
The expression receives the payload as its input ($). These variables are also bound: $eventType, $fromVersion and $toVersion.
/* v2 sends amount as { value, currency }; v1 receivers expect cents */
$merge([$, { "amount": amount.value }])/* 2026-10-01 nests the customer; v2 had a flat customerId */
$merge([$sift($, function($v, $k) { $k != "customer" }), { "customerId": customer.id }])If an expression throws (for example with $error("...")), returns nothing, or runs too long, that delivery isn't retried. The message moves straight to the dead letter queue with the reason version_transform_failed, and the error is shown on the message. Fix the expression, then retry the message from the DLQ.
Pinning subscriptions
| How the subscription was created | What it receives |
|---|---|
| Before the event type had versions | Pinned to the baseline version when the baseline is added |
Without eventVersion (dashboard, API, portal) | Pinned to the version that was latest at creation |
eventVersion: "latest" | Always the newest version |
eventVersion: "v2" | Always v2, until you change the pin |
In the dashboard, the Create Subscriptions dialog pins new subscriptions to the current latest version unless you tick Always follow the latest version. To move an existing subscription to another version, edit it and choose a version.
curl -X PATCH "https://api.hookbase.app/api/organizations/{orgId}/webhook-subscriptions/{subscriptionId}" \
-H "Authorization: Bearer whr_your_api_key" \
-H "Content-Type: application/json" \
-d '{ "eventVersion": "2026-10-01" }'Subscription responses include eventVersion ("latest" or the label), eventVersionDeprecated and eventVersionSunsetAt.
Sending a specific version
By default you send the latest version, and the payload is validated against the latest schema when payload validation is on. If part of your system still produces an older shape, say so:
curl -X POST "https://api.hookbase.app/v1/events" \
-H "Authorization: Bearer whr_your_api_key" \
-H "Content-Type: application/json" \
-d '{ "eventType": "order.paid", "eventVersion": "v1", "payload": { "id": "o1", "amount": 1250 } }'- The payload is validated against that version's schema.
- An unknown version is a
400("Unknown event version"). An event type without versions only accepts"latest". - The response includes
eventVersionfor a versioned event type.
The version is stored with the event, so replays and DLQ retries convert from the same version.
Deprecating and retiring versions
Edit an older version to mark it deprecated and set an optional sunset date. Deliveries keep working. Subscriptions pinned to it show a warning in the dashboard, and the API reports eventVersionDeprecated: true so you can remind integrators to move. The latest version can't be deprecated: add a newer version first.
You can delete the oldest or the newest version, as long as no subscription is pinned to it. A version in the middle can't be deleted because the downgrade chain would have a gap. When you delete the newest version, the previous one becomes the latest again.
Delivery history
The message detail page shows the version the event was sent in and the version that was delivered. Each attempt records the version it delivered too. Through the API, GET /outbound-messages/{id} returns eventVersion and deliveredEventVersion, and each attempt returns eventVersion.
Catalog export
The catalog export includes every version. The latest keeps the usual names, and older versions are added next to it with x-hookbase-event-version fixed to their label.
Notes
- Subscription filters evaluate the payload as you sent it, before any conversion.
- Labels are 1–32 characters: letters, digits,
.,_and-, starting with a letter or digit. They're case-sensitive and can't be changed.latestis reserved. Dates (2026-10-01) andv1,v2both work well. - An event type can have up to 50 versions.
- If you move to a plan below Starter, existing versions and pins keep working, and you can still edit, deprecate and delete versions. Adding versions and pinning subscriptions to a specific version need Starter or above. Choosing
latestis always allowed.
API reference
| Method | Path | |
|---|---|---|
GET | /event-types/{id}/versions | Versions, newest first, with isLatest and subscriptionCount |
POST | /event-types/{id}/versions | Add a version as the new latest: label, description, schema, examplePayload, downgradeExpression |
PATCH | /event-types/{id}/versions/{versionId} | Change description, schema, examplePayload, downgradeExpression, isDeprecated, sunsetAt |
DELETE | /event-types/{id}/versions/{versionId} | Delete the oldest or newest unpinned version |
POST | /event-types/{id}/versions/test-downgrade | Dry run one expression: expression, payload, targetVersion |
POST | /event-types/{id}/versions/convert | Dry run the chain: from, to, optional payload (defaults to the from version's example) |
All paths are under /api/organizations/{orgId}.