Tích hợp

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.

Cập nhật lần cuối

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.

The Demo booking webhook endpoint, active, with its URL, its two subscribed event types, and Test and Deliveries buttons

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#

  1. Select New endpoint.
  2. Give it a name.
  3. Enter the target URL.
  4. Select the event types to subscribe to.
  5. 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.

Quan trọng

http://localhost:9999 will 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#

  1. Select Deliveries on the endpoint.

A delivery log of failed deliveries, each reading

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#

  1. Find the row in the delivery log.
  2. Select Replay.

A failed delivery being replayed, flipping in place to pending with zero attempts

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.

Mẹo

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 id as the original. A consumer that dedupes on id treats 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.

Cảnh báo

booking.new means 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 raises folio.payment.taken for the payment, and nothing else.

So a consumer that wants to hear about all new business cannot subscribe to booking.new alone. Subscribe to the folio events as well, or poll /v1/bookings on 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#