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

# Webhooks & Events

Receive real-time notifications when resources change in anny. Register a webhook subscription, and the platform will POST event payloads to your URL.

For general request conventions, see [JSON-API Conventions](/developers/guides/json-api-conventions). For authentication, see [Authentication](/developers/guides/authentication).

***

## Prerequisites

* A valid Bearer token with admin access
* An HTTPS endpoint capable of receiving POST requests
* Organization context (`?o={organization_id}`)

***

<Tabs>
  <Tab title="Creating Webhooks via the Admin Dashboard">
    The easiest way to set up webhooks is through the anny admin dashboard:

    1. Navigate to **Organization Settings → API → Webhooks**
    2. Click **Create Webhook**
    3. Enter your HTTPS endpoint URL, select the events to subscribe to, and save

    For a step-by-step walkthrough with screenshots, see the [help center article on webhooks](https://docs.anny.co/en/articles/349740-webhooks).
  </Tab>

  <Tab title="Creating a Webhook Subscription via API">
    Request path documentation: `paths/admin/webhooks.yaml`

    ```http theme={null}
    POST /api/v1/webhook-subscriptions?o={org_id}

    {
      "data": {
        "type": "webhook-subscriptions",
        "attributes": {
          "name": "My Integration",
          "url": "https://example.com/webhooks/anny",
          "events": ["bookings.created", "bookings.updated", "orders.created"]
        }
      }
    }
    ```

    ### Attributes

    | Attribute        | Type      | Required | Description                                           |
    | ---------------- | --------- | -------- | ----------------------------------------------------- |
    | `name`           | string    | Yes      | Descriptive label for the subscription                |
    | `url`            | string    | Yes      | HTTPS endpoint to receive webhook POSTs               |
    | `events`         | string\[] | Yes      | Array of event identifiers to subscribe to            |
    | `custom_headers` | object    | No       | Additional headers sent with each webhook request     |
    | `is_active`      | boolean   | No       | Defaults to `true`. Set to `false` to pause delivery. |

    ### Response

    The response includes a `signing_key` — store this securely. It is auto-generated (64-character random string) and used to verify payload signatures.

    ```json theme={null}
    {
      "data": {
        "type": "webhook-subscriptions",
        "id": "ws-uuid",
        "attributes": {
          "name": "My Integration",
          "url": "https://example.com/webhooks/anny",
          "events": ["bookings.created", "bookings.updated", "orders.created"],
          "signing_key": "a1b2c3...64chars",
          "is_active": true,
          "failure_count": 0,
          "created_at": "2026-03-25T10:00:00+00:00"
        }
      }
    }
    ```
  </Tab>
</Tabs>

## Event Catalog

<AccordionGroup>
  <Accordion title="Bookings">
    | Event                  | Triggered when                                  |
    | ---------------------- | ----------------------------------------------- |
    | `bookings.created`     | A new booking is created                        |
    | `bookings.updated`     | Booking attributes change (status, dates, etc.) |
    | `bookings.started`     | Booking start time is reached                   |
    | `bookings.ended`       | Booking end time is reached                     |
    | `bookings.checked-in`  | Customer checks in                              |
    | `bookings.checked-out` | Customer checks out                             |
    | `bookings.deleted`     | Booking is permanently deleted                  |
  </Accordion>

  <Accordion title="Orders">
    | Event            | Triggered when                            |
    | ---------------- | ----------------------------------------- |
    | `orders.created` | Checkout is completed and order is placed |
    | `orders.updated` | Order attributes change                   |
    | `orders.deleted` | Order is permanently deleted              |
  </Accordion>

  <Accordion title="Invoices">
    | Event                | Triggered when                          |
    | -------------------- | --------------------------------------- |
    | `invoices.created`   | Invoice is generated                    |
    | `invoices.finalised` | Invoice is locked and assigned a number |
    | `invoices.updated`   | Invoice attributes change               |
    | `invoices.deleted`   | Invoice is deleted                      |
  </Accordion>

  <Accordion title="Customers">
    | Event               | Triggered when             |
    | ------------------- | -------------------------- |
    | `customers.created` | Customer record is created |
    | `customers.updated` | Customer data changes      |
    | `customers.deleted` | Customer is deleted        |
  </Accordion>

  <Accordion title="Subscriptions (Plans)">
    | Event                       | Triggered when                       |
    | --------------------------- | ------------------------------------ |
    | `subscriptions.created`     | Subscription is created              |
    | `subscriptions.started`     | Subscription period begins           |
    | `subscriptions.updated`     | Subscription attributes change       |
    | `subscriptions.renewed`     | Subscription renews for a new period |
    | `subscriptions.charged`     | Subscription payment is processed    |
    | `subscriptions.suspended`   | Subscription is paused               |
    | `subscriptions.canceled`    | Subscription is cancelled            |
    | `subscriptions.reactivated` | Suspended subscription resumes       |
    | `subscriptions.ended`       | Subscription expires                 |
  </Accordion>

  <Accordion title="Timeslots">
    | Event               | Triggered when                 |
    | ------------------- | ------------------------------ |
    | `timeslots.created` | Timeslot is created            |
    | `timeslots.updated` | Timeslot changes               |
    | `timeslots.started` | Timeslot start time is reached |
    | `timeslots.ended`   | Timeslot end time is reached   |
    | `timeslots.deleted` | Timeslot is deleted            |
  </Accordion>

  <Accordion title="Communities">
    | Event                       | Triggered when            |
    | --------------------------- | ------------------------- |
    | `community-accounts.joined` | Member joins a community  |
    | `community-accounts.left`   | Member leaves a community |
  </Accordion>
</AccordionGroup>

***

## Payload Structure

Every webhook delivery is a POST request with `Content-Type: application/json`. The payload uses a **normalized flat JSON** format — not JSON-API.

```json theme={null}
{
  "event": "bookings.updated",
  "event_id": "evt-a1b2c3d4-...",
  "webhook_id": "ws-uuid",
  "triggered_at": "2026-03-25T10:00:00+00:00",
  "data": {
    "id": "b1",
    "type": "bookings",
    "start_date": "2026-04-01T10:00:00+02:00",
    "end_date": "2026-04-01T11:00:00+02:00",
    "status": "accepted",
    "resource": {
      "id": "r1",
      "type": "resources",
      "name": "Meeting Room A",
      "slug": "meeting-room-a"
    },
    "service": {
      "id": "s1",
      "type": "services",
      "name": "Standard Hourly"
    },
    "customer": {
      "id": "c1",
      "type": "customers",
      "first_name": "Jane",
      "last_name": "Doe"
    }
  }
}
```

### Key differences from JSON-API

|               | JSON-API (management endpoints)             | Webhook payload                         |
| ------------- | ------------------------------------------- | --------------------------------------- |
| Attributes    | Nested under `data.attributes`              | Flat on `data` top level                |
| Relationships | Under `data.relationships` as ID references | Nested as full objects under their name |
| Side-loaded   | `included` array                            | Not used — relations are inline         |
| Content-Type  | `application/vnd.api+json`                  | `application/json`                      |

### Included relations per entity

| Entity             | Included relations                                                       |
| ------------------ | ------------------------------------------------------------------------ |
| Bookings           | `resource`, `resource.parent`, `service`, `customer`, `customer.address` |
| Orders             | `customer`, `bookings`                                                   |
| Invoices           | `customer`, `order`                                                      |
| Customers          | `address`                                                                |
| Subscriptions      | `customer`, `plan`                                                       |
| Timeslots          | `resource`, `service`                                                    |
| Community Accounts | `customer`, `community`                                                  |

<Note>
  Included relations and fieldsets may vary by event. The table above shows typical inclusions.
</Note>

***

## Signature Verification

Every webhook request includes a `Signature` header containing an HMAC SHA-256 hash of the request body, signed with your subscription's `signing_key`.

### Verifying the Signature

```text theme={null}
received_signature = request.headers["Signature"]
expected_signature = HMAC-SHA256(signing_key, request.body)

if received_signature != expected_signature:
    reject request (return 401)
```

<Warning>
  Always verify the signature before processing the payload. This prevents spoofed requests.
</Warning>

***

## Retry Policy

If your endpoint doesn't respond with a `2xx` status code within **10 seconds**, the delivery is retried:

| Attempt | Timing              |
| ------- | ------------------- |
| 1st     | Immediate           |
| 2nd     | Exponential backoff |
| 3rd     | Exponential backoff |

After **3 failed attempts** for a single event, that delivery is abandoned.

***

## Auto-Deactivation

If deliveries fail repeatedly across events, the subscription's `failure_count` increments. After **5 consecutive failures**, the subscription is automatically deactivated:

* `is_active` is set to `false`
* `failure_count` resets to `0`
* Organization admins receive a notification about the deactivation

To reactivate, update the subscription:

```http theme={null}
PATCH /api/v1/webhook-subscriptions/{id}?o={org_id}

{
  "data": {
    "type": "webhook-subscriptions",
    "id": "{id}",
    "attributes": {
      "is_active": true
    }
  }
}
```

***

## Managing Subscriptions

### List All Subscriptions

```http theme={null}
GET /api/v1/webhook-subscriptions?o={org_id}
```

### Update Events

```http theme={null}
PATCH /api/v1/webhook-subscriptions/{id}?o={org_id}

{
  "data": {
    "type": "webhook-subscriptions",
    "id": "{id}",
    "attributes": {
      "events": ["bookings.created", "bookings.updated", "customers.created"]
    }
  }
}
```

### Delete a Subscription

```http theme={null}
DELETE /api/v1/webhook-subscriptions/{id}?o={org_id}
```

***

## Best Practices

1. **Respond quickly** — return `200` immediately and process the payload asynchronously. The 10-second timeout is strict.
2. **Handle duplicates** — use `event_id` to deduplicate. The same event may be delivered more than once on retries.
3. **Verify signatures** — always validate the `Signature` header before trusting the payload.
4. **Subscribe selectively** — only subscribe to events you actually process to minimize traffic.
5. **Monitor `failure_count`** — periodically check subscription status to catch issues before auto-deactivation.

***

## Normalized JSON via Accept Header

Webhook management endpoints (and all other API `GET` endpoints) also support `Accept: application/json` to receive the same flat/normalized format used in webhook payloads.

```http theme={null}
GET /api/v1/webhook-subscriptions?o={org_id}
Accept: application/json
```

This returns attributes at the top level instead of nested under `data.attributes`, matching the webhook payload structure.

> `POST` and `PATCH` requests must still use `Content-Type: application/vnd.api+json` with standard JSON-API format.
