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

# Webhooks

> Receive signed, real-time notifications at your own endpoint whenever lease data changes in Spacebase.

Webhooks let Spacebase notify your systems the moment something changes. When a record you care about is created, updated, or deleted — a lease, a contact, an invoice — Spacebase sends a signed HTTPS POST request to a URL you choose. Use webhooks to keep data warehouses in sync, trigger downstream workflows, or feed integrations without polling the API.

## Who can use webhooks

<Info>
  Webhooks are available on the **Enterprise** plan and require an API token — the same access used for the [Enterprise API](/api/overview). If you don't have an API token yet, Spacebase directs you to the Enterprise API request page when you open the Webhooks section.
</Info>

A few things to know about access:

* The **Webhooks** link appears in the **Account** menu only for companies on an Enterprise plan.
* Webhooks are scoped to your company. You only receive events for your own data, and you can only manage your own endpoints.
* **Invoice** and **payment** events are available only when the payments module is enabled for your company.

## Create a webhook

<Steps>
  <Step title="Open the Webhooks page">
    Click the **Account** menu in the top right corner and select **Webhooks**.
  </Step>

  <Step title="Click Add Webhook">
    The **Add Webhook** button is in the top right of the Webhooks page.
  </Step>

  <Step title="Fill in the endpoint details">
    * **Endpoint URL** — where Spacebase sends events. The URL must use `https://`. Internal hosts (such as `localhost`, `*.local`, `*.internal`) and private IP addresses are not allowed.
    * **Description** (optional) — a short note to help your team identify the endpoint.
    * **Events** — check each event type you want to receive. See [Available events](#available-events) below.
    * **Active** — leave this checked to start receiving events right away. Uncheck it to pause delivery without deleting the webhook.
  </Step>

  <Step title="Save and copy your signing secret">
    Click **Save**. A confirmation message displays your signing secret.
  </Step>
</Steps>

<Warning>
  Your signing secret is shown **only once**, immediately after you create the webhook. Copy it and store it securely. If you lose it, rotate the secret to generate a new one.
</Warning>

## Edit and manage webhooks

The Webhooks page (**Account → Webhooks**) lists all of your endpoints with their URL, subscribed events, active status, and creation date. From here you can manage each webhook:

* **Edit** — opens the webhook so you can change the URL, description, subscribed events, or active status.
* **Deliveries** — opens the delivery log for that endpoint. See [Monitor deliveries](#monitor-deliveries-and-retries) below.

On the edit page you'll also find two more actions in the header:

* **Rotate secret** — generates a new signing secret and shows it once. The old secret stops working immediately, so update your receiving service at the same time.
* **Delete webhook** — removes the endpoint after a confirmation step. Deleting a webhook also removes its delivery history.

<Tip>
  To pause a webhook temporarily — during maintenance on your receiving service, for example — edit it and uncheck **Active** instead of deleting it. Reactivate it when you're ready to receive events again.
</Tip>

## Available events

Event names follow the pattern `resource.action`, where the action is `created`, `updated`, or `deleted`. For example, `lease.created` fires when a new lease is added.

| Resource           | Events                                                                                   |
| ------------------ | ---------------------------------------------------------------------------------------- |
| Lease              | `lease.created`, `lease.updated`, `lease.deleted`                                        |
| Area               | `area.created`, `area.updated`, `area.deleted`                                           |
| Contact            | `contact.created`, `contact.updated`, `contact.deleted`                                  |
| Entity             | `entity.created`, `entity.updated`, `entity.deleted`                                     |
| Custom field value | `custom_field_value.created`, `custom_field_value.updated`, `custom_field_value.deleted` |
| Option             | `option.created`, `option.updated`, `option.deleted`                                     |
| Deposit            | `deposit.created`, `deposit.updated`, `deposit.deleted`                                  |
| Lease file         | `lease_file.created`, `lease_file.updated`, `lease_file.deleted`                         |
| Critical date      | `critical_date.created`, `critical_date.updated`, `critical_date.deleted`                |
| Expense            | `expense.created`, `expense.updated`, `expense.deleted`                                  |
| Expense schedule   | `expense_schedule.created`, `expense_schedule.updated`, `expense_schedule.deleted`       |
| TI allowance       | `ti_allowance.created`, `ti_allowance.updated`, `ti_allowance.deleted`                   |
| Lease clause       | `lease_clause.created`, `lease_clause.updated`, `lease_clause.deleted`                   |
| Invoice            | `invoice.created`, `invoice.updated`, `invoice.deleted`                                  |
| Payment            | `payment.created`, `payment.updated`, `payment.deleted`                                  |

<Note>
  Invoice and payment events appear in the event list only when the payments module is enabled for your company.
</Note>

## Payload format

Every delivery is a JSON POST request with the same top-level structure:

```json theme={null}
{
  "event": "lease.updated",
  "occurred_at": "2026-07-07T14:32:11+00:00",
  "company": "your-company-uid",
  "data": {
    "resource": "lease",
    "action": "updated",
    "id": 123,
    "uid": "lease-uid",
    "label": "100 Main Street",
    "changes": {
      "Name": ["100 Main St", "100 Main Street"],
      "Expiration Date": ["2026-12-31", "2027-12-31"]
    },
    "object": {
      "id": 123,
      "uid": "lease-uid",
      "name": "100 Main Street"
    }
  }
}
```

* `event` — the event type, matching one of your subscriptions.
* `occurred_at` — when the change happened, in ISO 8601 format.
* `company` — your company's unique identifier.
* `data.changes` — the fields that changed, each with its before and after values.
* `data.object` — the full record, serialized the same way as the Enterprise API. On `deleted` events the record no longer exists, so `object` is omitted and only `id` and `label` identify what was removed.

## Verify signatures

Every request includes three headers:

| Header                  | Contents                                                                |
| ----------------------- | ----------------------------------------------------------------------- |
| `X-Spacebase-Event`     | The event type, such as `lease.updated`                                 |
| `X-Spacebase-Delivery`  | A unique ID for this delivery, useful for deduplication and support     |
| `X-Spacebase-Signature` | `sha256=` followed by the HMAC-SHA256 signature of the raw request body |

Verify the signature on every request before trusting the payload. Compute an HMAC-SHA256 of the **raw request body** using your signing secret, then compare it to the header value:

```python theme={null}
import hashlib
import hmac

def verify_signature(secret: str, raw_body: bytes, signature_header: str) -> bool:
    expected = hmac.new(secret.encode(), raw_body, hashlib.sha256).hexdigest()
    return hmac.compare_digest(f"sha256={expected}", signature_header)
```

<Tip>
  Use a constant-time comparison (like `hmac.compare_digest` above) rather than `==`, and always sign the raw bytes of the body — parsing and re-serializing the JSON first can change the byte sequence and break verification.
</Tip>

## Monitor deliveries and retries

Spacebase considers a delivery successful when your endpoint responds with a 2xx status code within 10 seconds. If the request fails — a connection error, timeout, or non-2xx response — Spacebase retries up to 5 more times with exponential backoff, waiting up to 10 minutes between attempts.

To review delivery history, click **Deliveries** next to any webhook on the Webhooks page. The log shows each delivery's event type, status (pending, succeeded, or failed), HTTP response code, attempt count, and timestamp, with 50 deliveries per page.

To resend a delivery — after fixing an issue on your end, for example — click **Redeliver** on that row. Spacebase queues a new delivery with the original payload.

<Note>
  Deliveries can occasionally arrive more than once (for example, after a redelivery or a retry that crossed with a slow response). Use the `X-Spacebase-Delivery` header to deduplicate on your side.
</Note>

## Manage webhooks with the API

Everything you can do in the UI is also available through the [Enterprise API](/api/overview) using your API token:

| Action                    | Request                                                                      |
| ------------------------- | ---------------------------------------------------------------------------- |
| List webhooks             | `GET /api/webhooks/`                                                         |
| Create a webhook          | `POST /api/webhooks/`                                                        |
| Retrieve a webhook        | `GET /api/webhooks/{id}/`                                                    |
| Update a webhook          | `PUT` or `PATCH /api/webhooks/{id}/`                                         |
| Delete a webhook          | `DELETE /api/webhooks/{id}/`                                                 |
| Rotate the signing secret | `POST /api/webhooks/{id}/rotate-secret/`                                     |
| List recent deliveries    | `GET /api/webhooks/{id}/deliveries/` (last 100)                              |
| Redeliver                 | `POST /api/webhooks/{id}/redeliver/` with body `{"delivery": <delivery_id>}` |

To create a webhook via the API:

```json theme={null}
POST /api/webhooks/
{
  "url": "https://example.com/spacebase/hooks",
  "description": "Data warehouse sync",
  "subscribed_events": ["lease.created", "lease.updated", "lease.deleted"],
  "is_active": true
}
```

The response to a create or rotate-secret request includes the full signing secret — this is the only time the API returns it. On every other request the secret is masked, showing only its last four characters.

## Troubleshooting

<AccordionGroup>
  <Accordion title="My webhook isn't receiving events">
    Check the following, in order:

    1. The webhook is marked **Active** on its edit page.
    2. The event you expect is checked under **Events** — webhooks only receive events they're subscribed to.
    3. The change happened to a resource type in the [event list](#available-events). Other changes don't fire webhooks.
    4. Open **Deliveries** for the endpoint. If deliveries appear but show as failed, your endpoint is being reached but isn't returning a 2xx response — the HTTP code and response details in the log will point to the cause.
  </Accordion>

  <Accordion title="My endpoint URL is rejected">
    Endpoint URLs must use `https://`. URLs pointing at internal hosts — `localhost`, addresses ending in `.local` or `.internal`, and private or reserved IP addresses — are not accepted. Use a publicly reachable HTTPS address.
  </Accordion>

  <Accordion title="I lost my signing secret">
    Secrets can't be recovered — they're only shown in full when created or rotated. Open the webhook's edit page and click **Rotate secret** to generate a new one, then update your receiving service. The old secret stops working as soon as you rotate.
  </Accordion>

  <Accordion title="Why don't I see invoice or payment events?">
    Invoice and payment events are only available when the payments module is enabled for your company. Contact your Spacebase representative if you'd like to enable it.
  </Accordion>

  <Accordion title="I can't find the Webhooks page">
    The **Webhooks** link in the **Account** menu appears only for companies on an Enterprise plan, and opening it requires an API token. If you're on Enterprise but don't have a token, you'll be directed to the Enterprise API request page to get started.
  </Accordion>
</AccordionGroup>
