> ## Documentation Index
> Fetch the complete documentation index at: https://docs.unifystays.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Webhooks

> Receive booking lifecycle events, test your endpoint, and inspect or replay deliveries.

Webhooks notify your backend when a booking changes, so you do not need to poll
for every status transition. You can configure the same webhook features in the
Unifystays portal or through the API.

## Before You Start

* Use a sandbox API key while developing and a production API key for live
  bookings.
* Create endpoints separately in each environment. Sandbox events are never
  delivered to production endpoints, or vice versa.
* Your destination must be a public HTTPS URL. URLs containing credentials,
  fragments, or private/internal hosts are rejected.
* Return a `2xx` response within 10 seconds. Process longer-running work
  asynchronously after acknowledging the request.

## 1. Create an Endpoint

```bash theme={null}
curl "$UNIFYSTAYS_BASE_URL/webhooks/endpoints" \
  -X POST \
  -H "content-type: application/json" \
  -H "x-api-key: $UNIFYSTAYS_API_KEY" \
  -d '{
    "name": "Production booking events",
    "url": "https://example.com/webhooks/unifystays",
    "event_types": ["booking.*"]
  }'
```

The response includes a `signing_secret`. Store it in your secret manager when
it is returned; it cannot be retrieved later.

<Warning>
  Never log the signing secret or expose it in browser or mobile code. Webhook
  verification belongs in your backend.
</Warning>

## 2. Verify Every Request

Unifystays signs the exact request body with HMAC-SHA256. Verify the signature
before parsing or processing the event, and reject stale timestamps to reduce
replay risk.

[Implement signature verification](/guides/verify-webhook-signatures)

## 3. Send a Test Event

```bash theme={null}
curl "$UNIFYSTAYS_BASE_URL/webhooks/endpoints/$ENDPOINT_ID/test" \
  -X POST \
  -H "x-api-key: $UNIFYSTAYS_API_KEY"
```

The endpoint must be `ACTIVE`. The API returns `202 Accepted` with an `event_id`
and `delivery_id`. Use the delivery ID to inspect the receiver's HTTP response.

```bash theme={null}
curl "$UNIFYSTAYS_BASE_URL/webhooks/deliveries/$DELIVERY_ID" \
  -H "x-api-key: $UNIFYSTAYS_API_KEY"
```

## Event Types

| Event                            | Sent when                                                   |
| -------------------------------- | ----------------------------------------------------------- |
| `booking.created`                | A booking is created in `PROCESSING`.                       |
| `booking.confirmed`              | The booking becomes `CONFIRMED`.                            |
| `booking.failed`                 | The booking becomes `FAILED`.                               |
| `booking.cancellation_requested` | The booking enters `CANCELLATION_PENDING`.                  |
| `booking.cancelled`              | Cancellation completes and the booking becomes `CANCELLED`. |
| `booking.cancellation_failed`    | A cancellation attempt fails.                               |

Subscribe to individual event names or use `booking.*` for every booking event.
Test deliveries use `webhook.test` and are sent only when you explicitly request
a test.

## Event Payload

```json theme={null}
{
  "id": "evt_3bc2fa3d35cc4bbf98359b3fb85a4fb2",
  "type": "booking.confirmed",
  "schema_version": 1,
  "created_at": "2026-08-08T08:30:00.000Z",
  "organization_id": "org_123",
  "data": {
    "object": {
      "object": "booking",
      "id": "ubk_eGQ0dW9uV1Vi",
      "version": 2,
      "status": "CONFIRMED",
      "is_terminal": true,
      "provider_code": "TRIPJACK",
      "hotel_id": "13553916",
      "check_in": "2026-09-12",
      "check_out": "2026-09-15",
      "total_payable_now": 428.5,
      "currency": "USD",
      "provider_booking_id": "TJ-983421",
      "provider_booking_code": "TJ-983421",
      "hotel_confirmation_number": "HCN-82219",
      "failure_code": null,
      "failure_message": null,
      "cancellation_charge": null,
      "cancellation_currency": null,
      "created_at": "2026-08-08T08:25:00.000Z",
      "updated_at": "2026-08-08T08:30:00.000Z"
    },
    "previous_status": "PROCESSING"
  }
}
```

For `booking.cancellation_failed`, `data.cancellation_error` contains the
failure message. Treat payloads as forward-compatible: ignore fields you do not
recognize and use `schema_version` when introducing version-specific handling.

## Delivery Guarantees

Webhook delivery is **at least once**. A successful request may be delivered
again, and events can arrive out of order.

* Deduplicate using the top-level event `id`.
* For booking state, apply only a payload whose `data.object.version` is newer
  than the version you have already processed.
* Return any `2xx` status after safely persisting or enqueueing the event.
* Do not depend on delivery order.

## Retries and Automatic Disabling

Failed deliveries are attempted up to eight times. Default delays after each
failure are 1 minute, 5 minutes, 30 minutes, 2 hours, 8 hours, 24 hours, and 48
hours. A valid `Retry-After` response header is honored, capped at 48 hours.

| Receiver response                  | Result                                                                |
| ---------------------------------- | --------------------------------------------------------------------- |
| Any `2xx`                          | Delivery succeeds.                                                    |
| `410 Gone`                         | Endpoint is disabled immediately and queued deliveries are cancelled. |
| Other non-`2xx` or network failure | Delivery is retried until exhausted.                                  |
| 50 consecutive failures            | Endpoint is disabled and queued deliveries are cancelled.             |

Re-activate a disabled endpoint by updating its status to `ACTIVE`, then replay
any delivery you still need.

## Logs and Retention

Delivery attempts record status, duration, safe response headers, an error, and
up to 16 KiB of response body. Response headers and bodies are cleared after 30
days. Completed events and deliveries are retained for 90 days.

Use [List Webhook Deliveries](/api-reference/webhooks/list-deliveries) for
filtering and [Get Webhook Delivery](/api-reference/webhooks/get-delivery) for
the complete attempt history.
