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

# Webhooks

> Restoo provides webhooks that allow external systems to receive real-time notifications whenever data is created, updated, or removed.

## How does a Webhook work?

A webhook is an `HTTP POST` request sent by Restoo to an HTTPS endpoint you control. The request is triggered automatically whenever an event occurs.

For additional information on Webhooks, there are a number of good resources:

* [Webhooks – The Definitive Guide](https://webhook.net/).
* [Webhook.site](https://webhook.site/) is a great tool for testing webhooks.

## Restoo Webhooks

Restoo Webhooks comply with the **[Standard Webhooks initiative](https://www.standardwebhooks.com/)**.

Your webhook handler must:

* Be publicly accessible via **HTTPS**.
* Return **HTTP 200 OK** to acknowledge the delivery.
* Respond within **10 seconds**.
* Handle retries correctly (see [Retry Policy](#retry-policy)).

To verify that incoming requests were genuinely sent by Restoo, follow the steps in [Securing Webhooks](#securing-webhooks).

Each webhook includes a set of **HTTP headers** and a **JSON payload**.

### Headers

The request will be sent with the following HTTP headers:

| **HTTP Header**     | **Description**                                                              |
| :------------------ | :--------------------------------------------------------------------------- |
| `User-Agent`        | Identifies the Restoo webhook client and version `Restoo-Webhook/<version>`. |
| `Webhook-Id`        | Unique identifier for the delivery `msg_<uuid>`.                             |
| `Webhook-Timestamp` | Unix timestamp in seconds (UTC) when the payload was generated.              |
| `Webhook-Signature` | HMAC-SHA256 signature used to verify authenticity.                           |

```http Example webhook headers theme={null}
Content-Type: application/json
User-Agent: Restoo-Webhook/3.0
Webhook-Id: msg_4a0b46384b1743ada0dc6f9dde035743
Webhook-Timestamp: 1763213177
Webhook-Signature: v1,9sJw+PE790f/fRe3ufg4o2ghTi79IEVGR0ZGeMbefyU=
```

### Payload

All webhook payloads share the same top-level structure:

| **Field**   | **Description**                                                                                                                                                     |
| :---------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `type`      | Dot-delimited event type with `<resource>.<event>` format (e.g `booking.created`) where `<resource>` determines the schema of the payload (passed in `data` field). |
| `timestamp` | ISO-8601 UTC timestamp (`YYYY-MM-DDTHH:mm:ssZ`) of when the event occurred.                                                                                         |
| `version`   | Payload schema version.                                                                                                                                             |
| `data`      | Resource affected by the event (e.g., booking, customer).                                                                                                           |

```json Example webhook payload theme={null}
{
  "type": "booking.updated",
  "timestamp": "2025-11-18T19:22:31Z",
  "version": "3.0.0",
  "data": {
    // ...event-specific content...
  }
}
```

## Retry Policy

A webhook attempt is considered failed when your endpoint:

* Returns a **non‑2xx** code.
* Does not respond within **10 seconds**.
* Is unreachable.

Restoo retries delivery **up to 5 times** (6 total attempts) using the following backoff schedule:

| Attempt | Delay after previous attempt |
| ------- | ---------------------------- |
| 2nd     | 5 seconds                    |
| 3rd     | 30 seconds                   |
| 4th     | 5 minutes                    |
| 5th     | 15 minutes                   |
| 6th     | 30 minutes                   |

All retries carry the same `Webhook-Id`, `Webhook-Timestamp`, and `Webhook-Signature` headers as the original attempt.

After all retries fail, the webhook is marked undeliverable.

<Warning>
  Your integration may be disabled if the endpoint consistently fails.
</Warning>

### Idempotency

Because of retries, your handler must be idempotent:

* Use `Webhook-Id` to detect already-processed deliveries.
* Skip duplicated attempts safely.

## Securing Webhooks

### Webhook Signing Secret

Each Partner is assigned a unique webhook signing secret used to verify the authenticity of all webhooks sent by Restoo.

* A single signing secret is generated **per Partner**.
* This secret is shared across all webhook deliveries associated with that Partner.
* The same secret must be used to verify every incoming webhook request.
* This secret must be kept **confidential** and never exposed in client-side code.

The signing secret is provided by Restoo in the following format:

```text theme={null}
whsec_<base64_encoded_secret>
```

You must use this secret to validate the `Webhook-Signature` header included in each webhook request.

```header Webhook-Signature format theme={null}
v1,<base64_hmac_sha256_signature>
```

<Info>
  Each environment (e.g. Dev and Prod) has its own signing secret. Make sure you
  are using the correct secret for the environment receiving the webhook.
</Info>

### How the signature is generated

Restoo generates a webhook signature following these steps:

1. Remove the `whsec_` prefix from your signing secret provided by Restoo.
2. Base64-decode the signing secret.
3. Build the signed content: `<Webhook-Id>.<Webhook-Timestamp>.<raw_body>`.
4. Compute HMAC-SHA256 of the signed content with binary output.
5. Base64-encode the output.
6. Build the final signature by prefixing it with the version identifier (`v1,`).

```php Example of signature generation expandable using PHP icon="php" theme={null}
// Signing secret provided by Restoo
$secret = 'whsec_dpDAewAkZGG1HMx2EBDTdvfqbMDzoraX';

// 1. Remove "whsec_" prefix
$secret = substr($secret, strlen('whsec_'));

// 2. Base64-decode the signing secret
$decodedSecret = base64_decode($secret, true);

if ($decodedSecret === false) {
    throw new RuntimeException('Invalid webhook signing secret (base64 decode failed).');
}

// 3. Build the signed content
$payload = $id . '.' . $timestamp . '.' . $rawBody;

// 4. Compute HMAC-SHA256 (binary output)
$hmac = hash_hmac(
    'sha256',
    $payload,
    $decodedSecret,
    true
);

// 5. Base64-encode the output
$encodedHmac = base64_encode($hmac);

// 6. Build the final signature
$signature = 'v1,' . $encodedHmac;
```

### Verification steps

To verify the authenticity, your webhook handler must:

1. Read the **raw request body** . Must be the exact byte-for-byte body received (no parsing, no reformatting).
2. Extract the following headers: `Webhook-Id`, `Webhook-Timestamp`, `Webhook-Signature`.
3. Recompute the signature using **HMAC-SHA256** with your **Base64-decoded** signing secret.
4. Remove the `v1,` prefix from the header.
5. Compare signatures using a **constant-time comparison** to prevent timing attacks.

```php Example of secure constant-time comparison using PHP icon="php" theme={null}
if (!hash_equals($expectedSignature, $receivedSignature)) {
    throw new Exception('Invalid webhook signature');
}
```

<Info>
  You may enforce a maximum timestamp age to reduce replay attacks. Make sure
  this window is compatible with Restoo retry delays.
</Info>

## Available Webhook Events

Each event includes a structured payload describing the resource affected.

| **Event**         | **Description**                                                                |
| :---------------- | :----------------------------------------------------------------------------- |
| `booking.created` | Triggered whenever a new booking is successfully created.                      |
| `booking.updated` | Triggered whenever an existing booking is modified (status, time, table, etc.) |

More events will be added over time. Unknown event types should be ignored safely.

<Warning>
  Your own API calls also trigger `booking.created` and `booking.updated`. If
  you create or update a booking and then receive a webhook for it, that webhook
  was caused by your own request. Use `Webhook-Id` for idempotency but do not
  assume these events always originate externally.
</Warning>
