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

# Webhooks

> Receive webhooks from your app or send them to external systems.

Webhooks allow you to receive real-time HTTP notifications when data changes in your Archie Core project. When a record is created, updated, or deleted, Archie Core sends a `POST` request to your configured URL with the event details.

## How webhooks work

1. **Register** a webhook subscription specifying the table, events, and target URL
2. **Data changes** — when a matching mutation occurs (via REST API or GraphQL), an event is published
3. **Delivery** — the webhook service sends an HTTP `POST` to your URL with the event payload
4. **Verification** — validate the HMAC signature to ensure the request is authentic

## Webhook management API

All webhook management endpoints are under `/api/rest/_webhooks` and require a management API token.

### Required headers

| Header          | Required | Description                     |
| --------------- | -------- | ------------------------------- |
| `Authorization` | Yes      | `Bearer <management-api-token>` |
| `X-Project-ID`  | Yes      | Your project identifier         |
| `environment`   | Yes      | Target environment name         |

> **Important:** The `environment` header is **required** for all webhook management endpoints. The header value must match the `environment` field in the request body when creating or updating webhooks.

### Endpoints

| Method   | Endpoint                  | Description                    |
| -------- | ------------------------- | ------------------------------ |
| `POST`   | `/api/rest/_webhooks`     | Create a webhook subscription  |
| `GET`    | `/api/rest/_webhooks`     | List all webhook subscriptions |
| `GET`    | `/api/rest/_webhooks/:id` | Get subscription + deliveries  |
| `PATCH`  | `/api/rest/_webhooks/:id` | Update a subscription          |
| `DELETE` | `/api/rest/_webhooks/:id` | Delete a subscription          |

## Create a webhook

**Request**

```bash theme={null}
curl -X POST "https://your-gateway.example.com/gw/api/rest/_webhooks" \
  -H "Authorization: Bearer your-management-token" \
  -H "X-Project-ID: your-project-id" \
  -H "Content-Type: application/json" \
  -d '{
    "projectId": "your-project-id",
    "environment": "master",
    "table": "orders",
    "events": ["INSERT", "UPDATE", "DELETE"],
    "url": "https://your-app.example.com/webhooks/orders",
    "secret": "whsec_your_signing_secret",
    "active": true
  }'
```

### Request body

| Field         | Type    | Required | Description                                   |
| ------------- | ------- | -------- | --------------------------------------------- |
| `projectId`   | String  | Yes      | Project identifier                            |
| `environment` | String  | Yes      | Target environment                            |
| `table`       | String  | Yes      | Table name to watch                           |
| `events`      | Array   | Yes      | Event types: `INSERT`, `UPDATE`, `DELETE`     |
| `url`         | String  | Yes      | HTTPS endpoint to receive events              |
| `secret`      | String  | Yes      | Secret for HMAC signature verification        |
| `active`      | Boolean | No       | Enable/disable the webhook (default: `true`)  |
| `filter`      | Object  | No       | Only trigger for records matching this filter |
| `select`      | Array   | No       | Fields to include in the payload              |

### Optional filtering

Only fire the webhook when specific conditions are met:

```json theme={null}
{
  "table": "orders",
  "events": ["UPDATE"],
  "url": "https://your-app.example.com/webhooks/completed-orders",
  "secret": "whsec_secret",
  "filter": { "status": "completed" }
}
```

### Field selection

Limit which fields are included in the event payload:

```json theme={null}
{
  "table": "orders",
  "events": ["INSERT"],
  "url": "https://your-app.example.com/webhooks/new-orders",
  "secret": "whsec_secret",
  "select": ["id", "total", "status", "customerId"]
}
```

**Response (201 Created)**

```json theme={null}
{
  "id": "wh_a1b2c3d4-e5f6-7890",
  "projectId": "your-project-id",
  "environment": "master",
  "table": "orders",
  "events": ["INSERT", "UPDATE", "DELETE"],
  "url": "https://your-app.example.com/webhooks/orders",
  "active": true,
  "createdAt": "2025-12-15T14:30:00Z"
}
```

## List webhooks

```bash theme={null}
curl -X GET "https://your-gateway.example.com/gw/api/rest/_webhooks" \
  -H "Authorization: Bearer your-management-token" \
  -H "X-Project-ID: your-project-id" \
  -H "environment: master"
```

## Get webhook details

Returns the webhook subscription along with its recent delivery history.

```bash theme={null}
curl -X GET "https://your-gateway.example.com/gw/api/rest/_webhooks/wh_a1b2c3d4-e5f6-7890" \
  -H "Authorization: Bearer your-management-token" \
  -H "X-Project-ID: your-project-id" \
  -H "environment: master"
```

## Update a webhook

```bash theme={null}
curl -X PATCH "https://your-gateway.example.com/gw/api/rest/_webhooks/wh_a1b2c3d4-e5f6-7890" \
  -H "Authorization: Bearer your-management-token" \
  -H "X-Project-ID: your-project-id" \
  -H "environment: master" \
  -H "Content-Type: application/json" \
  -d '{
    "events": ["INSERT", "UPDATE"],
    "active": false
  }'
```

## Delete a webhook

```bash theme={null}
curl -X DELETE "https://your-gateway.example.com/gw/api/rest/_webhooks/wh_a1b2c3d4-e5f6-7890" \
  -H "Authorization: Bearer your-management-token" \
  -H "X-Project-ID: your-project-id" \
  -H "environment: master"
```

## Event payload

When a data mutation occurs, the webhook service delivers an HTTP `POST` to your URL:

```json theme={null}
{
  "id": "evt_abc123def456",
  "timestamp": "2025-12-15T15:00:00Z",
  "project_id": "your-project-id",
  "environment": "master",
  "table": "orders",
  "event": "INSERT",
  "record": {
    "id": "f47ac10b-58cc-4372-a567-0e02b2c3d479",
    "total": 99.50,
    "status": "pending",
    "customerId": "cust-001"
  },
  "data": {
    "id": "f47ac10b-58cc-4372-a567-0e02b2c3d479",
    "total": 99.50,
    "status": "pending",
    "customerId": "cust-001"
  },
  "changed_fields": ["total", "status"]
}
```

| Field            | Description                                         |
| ---------------- | --------------------------------------------------- |
| `id`             | Unique event identifier                             |
| `timestamp`      | ISO 8601 timestamp of the event                     |
| `project_id`     | Project that generated the event                    |
| `environment`    | Environment where the mutation occurred             |
| `table`          | Table that was modified                             |
| `event`          | Event type: `INSERT`, `UPDATE`, or `DELETE`         |
| `record`         | The full record (or selected fields) after mutation |
| `data`           | Same as `record` (alias for compatibility)          |
| `changed_fields` | List of fields that changed (for `UPDATE` events)   |

> **Note:** For `DELETE` events, `record` and `data` contain the record as it was before deletion. The `changed_fields` array is empty for `INSERT` and `DELETE` events.

## Delivery headers

Each webhook delivery includes these headers:

| Header                | Value                          | Description                       |
| --------------------- | ------------------------------ | --------------------------------- |
| `Content-Type`        | `application/json`             | Payload format                    |
| `X-Archie-Event`      | `INSERT` / `UPDATE` / `DELETE` | Event type                        |
| `X-Archie-Delivery`   | `evt_abc123def456`             | Event ID for tracking             |
| `X-Archie-Signature`  | `sha256=<hex>`                 | HMAC-SHA256 signature with prefix |
| `X-Webhook-Signature` | `<hex>`                        | Raw HMAC-SHA256 hex digest        |

## Verifying signatures

Every delivery is signed using HMAC-SHA256 with the webhook's `secret`. Always verify the signature before processing the payload.

### Node.js example

```javascript theme={null}
const crypto = require('crypto');

function verifyWebhookSignature(payload, signature, secret) {
  const expected = crypto
    .createHmac('sha256', secret)
    .update(payload, 'utf8')
    .digest('hex');

  const sig = signature.replace('sha256=', '');
  return crypto.timingSafeEqual(
    Buffer.from(sig, 'hex'),
    Buffer.from(expected, 'hex')
  );
}

// In your webhook handler:
app.post('/webhooks/orders', (req, res) => {
  const signature = req.headers['x-archie-signature'];
  const isValid = verifyWebhookSignature(
    JSON.stringify(req.body),
    signature,
    'whsec_your_signing_secret'
  );

  if (!isValid) {
    return res.status(401).send('Invalid signature');
  }

  // Process the event
  const { event, table, record } = req.body;
  console.log(`${event} on ${table}:`, record);

  res.status(200).send('OK');
});
```

### Python example

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

def verify_webhook_signature(payload: bytes, signature: str, secret: str) -> bool:
    expected = hmac.new(
        secret.encode('utf-8'),
        payload,
        hashlib.sha256
    ).hexdigest()

    sig = signature.replace('sha256=', '')
    return hmac.compare_digest(sig, expected)
```

## Retry policy

If your endpoint returns a non-2xx response or is unreachable, the webhook service retries delivery with exponential backoff:

| Attempt | Delay        |
| ------- | ------------ |
| 1       | Immediate    |
| 2       | \~1 second   |
| 3       | \~5 seconds  |
| 4       | \~30 seconds |
| 5       | \~5 minutes  |

After the maximum retry attempts, the delivery is moved to a **dead letter** state and can be inspected via the webhook details endpoint.

### Circuit breaker

If an endpoint fails consecutively, the webhook service activates a circuit breaker to protect your endpoint:

* **Opens** after 5 consecutive failures per endpoint
* **Cooldown** period: 2 minutes
* During cooldown, deliveries are queued and retried after the cooldown expires

## Best practices

* **Always verify signatures** — never trust a webhook payload without HMAC verification
* **Respond quickly** — return a `200` status within 5 seconds; process events asynchronously if needed. Long-running handlers are the natural use case for [custom functions](/features/backend/custom-functions/overview) — the webhook receiver dispatches to a function, which acknowledges immediately and processes in the background.
* **Handle duplicates** — use the event `id` to implement idempotent processing
* **Use HTTPS** — webhook URLs must use HTTPS for secure delivery
* **Monitor deliveries** — check the webhook details endpoint regularly to catch persistent failures

## FAQ

<AccordionGroup>
  <Accordion title="Where do I find webhook URLs to register with the third-party service?">
    The integration's settings page in your project shows the URL to register.
  </Accordion>

  <Accordion title="What is the timeout for handlers?">
    Return a `200` status within 5 seconds. Process longer work asynchronously and acknowledge the delivery first.
  </Accordion>

  <Accordion title="Can I see a log of every webhook received?">
    Yes — the webhook details endpoint (`GET /api/rest/_webhooks/:id`) returns recent deliveries with status, HTTP code, attempts, and timestamp. {/* VERIFY: confirm log retention duration */}
  </Accordion>
</AccordionGroup>
