Webhooks
Manage your own webhook endpoints through the API, receive every connected partner's events on one endpoint, and route each event by its partnerId.
Localoy tells your server when something happens for a connected partner — one of your items changes,
a customer books, a payment settles, the partner changes your access — with a signed HTTPS POST. The
envelope, signatures and retries are the ones partners get. Three things differ:
- You manage your endpoints through the API, with your provider key. There is no portal for providers.
- One endpoint serves every partner. Each event carries the
partnerIdit belongs to. - What you receive follows each connection — the scopes it grants, your modules and its status.
One endpoint for every partner#
- Localoy → Your endpoint: POST event + X-Localoy-Signature
- Your endpoint → Your endpoint: Verify signature, skip repeats
- Your endpoint → Your job queue: Enqueue with partnerId
- Your endpoint → Localoy: 200 OK within 10 s
- Your job queue → Your job queue: Find the partner's account
- Your job queue → Your job queue: Apply the change
{
"id": "evt_mfz3k2a1Xq9vT0bLm3Rk",
"event": "booking.created",
"occurredAt": "2026-09-27T11:05:42.230Z",
"environment": "production",
"partnerId": "cm1partnerid000000000000001",
"data": { "booking": { "…": "…" } }
}Find the partner by partnerId — the ID you send as X-Localoy-Partner-Id. For a partner you no longer
know, answer 2xx anyway, so the event is not retried.
Set up an endpoint#
curl -X POST "$LOCALOY_BASE_URL/webhooks" \
-H "Authorization: Bearer $LOCALOY_PROVIDER_KEY" \
-H "Content-Type: application/json" \
-d '{
"url": "https://hooks.example.com/localoy",
"environment": "PRODUCTION",
"events": ["booking.created", "booking.updated", "booking.cancelled", "connection.updated", "connection.revoked"],
"description": "Production receiver"
}'| Field | Detail |
|---|---|
url | Where Localoy sends events: https, public, and within the URL requirements that apply to every Localoy webhook. |
environment | SANDBOX or PRODUCTION. An endpoint receives only its environment's events, and its environment cannot be changed later. |
events | The events to send it. At least one. |
description | Optional, up to 200 characters. |
The response carries the endpoint's signing secret, starting whsec_. Store it in your receiver. You
can reveal it again and
rotate it later.
You can hold up to 10 endpoints, and the same URL cannot be registered twice in one environment. Every endpoint operation is in the reference.
Events#
| Event | Sent when | You receive it if | Environment |
|---|---|---|---|
catalog.item.created, catalog.item.updated, catalog.item.deleted | An item is created, changed or deleted. | It is an item you created for that partner. | That of the key that made the change. |
booking.created, booking.updated, booking.cancelled | A customer books with the partner, or a booking changes or is cancelled. | The partner granted BOOKINGS, and the booking is in one of your modules. | Production. |
payment.updated | A booking's payment status changes. | The partner granted BOOKINGS and PAYMENT, and the booking is in one of your modules. | Production. |
connection.updated | The partner pauses you, resumes you or changes your scopes. | Always. | See Environments. |
connection.revoked | The partner disconnects you. | Always. | See Environments. |
ping | You call POST /webhooks/{id}/test. It cannot be subscribed to. | — | The endpoint's. |
Booking and payment events carry the same booking object partners receive, including
customerName, customerPhone and customerEmail — see Booking events.
Catalogue events carry the item, as in Catalogue events. The two
connection.* payloads are in Connection events; only
Technology Providers can subscribe to them.
Environments#
booking.*andpayment.updatedgo only to production endpoints: real bookings exist only in production.catalog.item.*go to your endpoints of the environment of the key that made the change.connection.*come from the partner's own actions in its Partner Portal, not from one of your keys, so they go to every endpoint subscribed to them in both environments, under one event ID. Each delivery'senvironmentis its endpoint's.- A test
pinggoes to the endpoint you tested, withpartnerIdnull.
When nothing is delivered#
| Situation | What you receive |
|---|---|
| The partner paused you. | connection.updated with reason paused, then no new events for that partner until connection.updated with reason resumed. Deliveries queued before the pause are still sent. Meanwhile, replaying one of its deliveries answers 409 webhook_replay_connection_inactive. |
| The partner disconnected you. | connection.revoked, then nothing for that partner — not even a catalog.item.deleted for the items removed with the connection; itemsRemoved counts them. Deliveries still queued for it are marked FAILED without being sent, and replaying one of its earlier deliveries answers 409 webhook_replay_connection_inactive. A connection.* delivery can still be replayed. |
| Localoy suspended you. | Nothing, to any of your endpoints, until Localoy resumes you. Deliveries already queued are marked FAILED without being sent; replay them once you are resumed. |
| Your endpoint is disabled. | Nothing to that endpoint. An endpoint is disabled automatically after 10 consecutive failed attempts. |
Verifying signatures#
Deliveries are signed exactly like partners' webhooks, each with its endpoint's own secret:
X-Localoy-Signature: t=1790500000,v1=5b1f0c9e4a…v1 is the hex HMAC-SHA256 of {t}.{raw body}, keyed with the endpoint's full whsec_… secret.
Follow Verifying signatures: its Node.js, PHP and Python code works unchanged.
The other headers — X-Localoy-Event-Id for de-duplication among them — are listed in
Receive an event.
Delivery and retries#
The policy is the one in Delivery and retries: answer 2xx within 10 seconds; a
failed delivery is retried, up to 6 attempts over about five minutes; and an endpoint is disabled after
10 consecutive failed attempts. Delivery is at least once, and in no guaranteed order.
Your delivery log#
| Task | Endpoint |
|---|---|
| List deliveries, by partner, event, status or endpoint | GET /webhooks/deliveries |
| Read one, with its payload and your endpoint's last answer | GET /webhooks/deliveries/{id} |
| Send it again | POST /webhooks/deliveries/{id}/replay |
| Switch a disabled endpoint back on | PATCH /webhooks/{id} with status ENABLED |
To recover after your endpoint was down:
- Your server → Localoy: POST /webhooks/{id}/test
- Localoy → Your endpoint: Signed ping
- Your endpoint → Localoy: 200
- Localoy → Your server: 200 ok: true
- Your server → Localoy: PATCH /webhooks/{id} ENABLED
- Your server → Localoy: GET /webhooks/deliveries?status=FAILED
- Localoy → Your server: The failed deliveries
- Your server → Localoy: POST …/{id}/replay, for each
- Localoy → Your endpoint: Same event ID, new delivery
A test works on a disabled endpoint. Enabling an endpoint resets its failure count. A replay keeps the event's ID, so a receiver that already processed the event recognises it.