Integrations
Webhooks, retries and replay
Signed delivery, six retries with backoff, and a replay that re-sends the same event id — which is what makes pressing it twice cost a consumer nothing.
Last updated
Who this is for: Owner — requires
integration.webhook.manage. Where: Integrations → Webhooks
A webhook endpoint is a URL of yours that Nerve posts events to as they happen, so your system does not have to poll. Each delivery is signed, retried on failure, kept in a log you can read, and replayable.

An endpoint is a URL, a list of event types and a switch. The signing secret is not on this card — it is shown once, when the endpoint is created.
Create an endpoint#
- Select New endpoint.
- Give it a name.
- Enter the target URL.
- Select the event types to subscribe to.
- Save, and copy the signing secret — like an API token, it is shown once.
The target URL has to be public HTTPS#
The rules are enforced twice, and they are not configurable:
| Rule | Why |
|---|---|
Scheme must be https |
Event bodies carry guest data |
| No credentials in the URL | https://user:pass@… is rejected |
| The host must not resolve to a private, loopback or link-local address | Otherwise an endpoint becomes a probe for the inside of the network it is running in |
The address check runs when you save and again at the moment of each delivery, against the IP actually dialled. That second check is what stops a hostname that passed validation from later resolving somewhere private.
Important
http://localhost:9999will not work, and neither will a tunnel to it that resolves to a private address. To develop against this you need a genuinely public HTTPS receiver. Point it at a throwaway host — an event body carries real guest data, so a shared "request bin" is a data leak, not a test fixture.
Use Test on a saved endpoint to send a connector.test delivery without waiting for something to happen in the hotel.
Verify a delivery is really from Nerve#
Every POST carries three headers:
| Header | Contents |
|---|---|
X-Nerve-Event |
The event type, e.g. booking.new |
X-Nerve-Delivery |
The delivery id — unique per attempt-chain, stable across replays |
X-Nerve-Signature |
sha256= followed by an HMAC-SHA256 of the raw request body, keyed with the endpoint's signing secret |
Compute the HMAC over the bytes you received, before any JSON parsing, and compare in constant time. A body that has been re-serialised will not match.
The body is the same envelope for every event type:
{
"id": "6929cb5d-06b5-4cf3-a19f-e65af1d7e06b",
"type": "booking.new",
"orgId": "1a520975-7319-4ac3-9d73-844770cc078a",
"createdAt": "2026-08-08T05:31:23Z",
"data": { }
}id is the event id, and it is the field to dedupe on. It is stable — the same event redelivered, whether by a retry or by a replay, carries the same id.
Read the delivery log#
- Select Deliveries on the endpoint.

Failure is visible, not silent. Six attempts, the actual transport error, and a Replay button on every row.
Each row is one event, and carries the event type, the status, the number of attempts, and — when it went wrong — the error verbatim, whether that is a TLS failure, a DNS failure or status 500.
How retries work#
| Attempts | 6, then the row rests as failed |
| Backoff | Doubling — 2s, 4s, 8s, 16s… capped at 5 minutes |
| Non-2xx | Treated as a failure and retried |
| Disabled or deleted endpoint | The delivery is parked at once rather than retried |
A failed row is not discarded. It stays in the log with its error, which is the point: an integration that quietly stopped receiving is the failure mode this design exists to prevent.
Replay a delivery#
- Find the row in the delivery log.
- Select Replay.

Replay in place. The row does not duplicate — the same delivery, carrying the same event id, goes back on the queue.
The row resets to pending with its attempt count cleared and its backoff dropped, and the dispatcher picks it up on its next pass.
Tip
Replay is safe to press twice. It re-queues the existing delivery rather than creating a new one, so no second row appears, and the redelivered event carries the same
idas the original. A consumer that dedupes onidtreats the replay as the event it already has. That is the whole contract: fix your receiver, replay everything that failed while it was down, and your system converges without you reasoning about which ones it already saw.
Replay does not re-evaluate anything. The body is the envelope as it was captured when the event happened, not a fresh read of the hotel's current state.
What you can subscribe to#
The seeded example endpoint subscribes to two of these, booking.new and folio.payment.taken, which between them cover "somebody booked" and "money arrived".
| Family | Events |
|---|---|
| Booking lifecycle | booking.new, booking.modification, booking.cancellation |
| Booking problems | booking.unmapped_room, booking.unmapped_rate, booking.non_acked |
| Folio | folio.charge.posted, folio.payment.taken, folio.payment.refunded |
| Channel state | channel.activated, channel.disconnected, channel.sync_error, channel.sync_warning, channel.rate_error |
| Distribution | ari.updated |
| Guest contact | message.received, review.received |
The Test button sends a connector.test delivery. It is not in the list above because it is not subscribable — it is a manual ping, and it arrives whatever the endpoint is subscribed to.
The booking-problem events are worth subscribing to even when nothing consumes them programmatically: booking.unmapped_room and booking.unmapped_rate are how you find out a channel sent something the property does not recognise, before somebody notices at the front desk.
Warning
booking.newmeans a booking arriving from a channel, not every new booking. The three booking events fan out from channel ingest — an OTA reservation landing, being amended, being cancelled. A direct booking taken through the booking widget does not raise one. It raisesfolio.payment.takenfor the payment, and nothing else.So a consumer that wants to hear about all new business cannot subscribe to
booking.newalone. Subscribe to the folio events as well, or poll/v1/bookingson a schedule for the direct side.
Endpoints are scoped to a property#
An endpoint created while you are in a property only receives events that name that property. An event that carries no property reaches organisation-wide endpoints only. A group with two hotels that wants both needs two endpoints.
What's next#
- API keys — for the reads your webhook consumer will make when it wants more than the envelope carries.
- Embedding the direct booking widget — the source of the direct bookings that
booking.newdoes not cover.