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

# Webhooks

> Register, update, and delete outbound webhook endpoints programmatically.

Manage outbound webhook endpoints programmatically. Every endpoint below requires the `control:webhooks` scope.

Webhooks registered here are the same endpoints visible in the **Webhooks** page of each app in the dashboard. Creating one via the API is equivalent to clicking **Add endpoint** in the UI — they share the same delivery infrastructure.

For information on the events that are delivered and how to verify signatures, see [Outbound Webhooks](/app-builder/reference/outbound-webhooks).

***

## List webhook endpoints

```
GET https://api.usehasp.com/v1/webhooks
Authorization: Bearer hasp_api_live_...
```

Returns all non-deleted webhook endpoints across every app in your org.

### Response

```json theme={null}
{
  "success": true,
  "data": [
    {
      "id": "01JQWH0000000000000000000",
      "app_id": "01JQAPP00000000000000000",
      "url": "https://example.com/hooks/hasp",
      "events": ["record.created", "record.updated"],
      "is_active": true,
      "description": "Production record sync",
      "created_at": "2026-05-01T10:00:00+00:00"
    }
  ]
}
```

The signing secret is **never** returned in list or detail responses — it is returned once at creation time only.

***

## Create a webhook endpoint

```
POST https://api.usehasp.com/v1/webhooks
Authorization: Bearer hasp_api_live_...
Content-Type: application/json
```

### Body

```json theme={null}
{
  "app_id": "01JQAPP00000000000000000",
  "url": "https://example.com/hooks/hasp",
  "events": ["record.created", "record.updated"],
  "description": "Production record sync"
}
```

| Field         | Type   | Required | Description                                                                                  |
| ------------- | ------ | -------- | -------------------------------------------------------------------------------------------- |
| `app_id`      | string | Yes      | ULID of the app to attach this endpoint to. Must belong to your org.                         |
| `url`         | string | Yes      | HTTPS destination URL. Max 2048 characters. Private/internal IPs and localhost are rejected. |
| `events`      | array  | Yes      | One or more event strings. Must be non-empty.                                                |
| `description` | string | No       | Human-readable label. Max 255 characters.                                                    |

**Valid events:** `record.created`, `record.updated`, `record.deleted`, `record.bulk_created`, `schema.updated`

### Response (201)

```json theme={null}
{
  "success": true,
  "data": {
    "id": "01JQWH0000000000000000000",
    "app_id": "01JQAPP00000000000000000",
    "url": "https://example.com/hooks/hasp",
    "events": ["record.created", "record.updated"],
    "is_active": true,
    "description": "Production record sync",
    "created_at": "2026-05-02T10:00:00+00:00",
    "secret": "hasp_obh_live_a3f9c2d8..."
  }
}
```

`data.secret` is the HMAC-SHA256 signing secret. **This is the only time it is returned.** Store it securely — it cannot be retrieved again. To replace it without downtime, use [Rotate the signing secret](#rotate-the-signing-secret) below.

***

## Update a webhook endpoint

```
PATCH https://api.usehasp.com/v1/webhooks/{endpoint_id}
Authorization: Bearer hasp_api_live_...
Content-Type: application/json
```

Every field is optional — only fields present in the body are changed. Omit a field to leave it unchanged; send `description: null` to clear it.

### Body

```json theme={null}
{
  "url": "https://example.com/hooks/hasp-v2",
  "events": ["record.created", "record.deleted"],
  "description": "Production record sync (v2)"
}
```

| Field         | Type           | Description                                                                                      |
| ------------- | -------------- | ------------------------------------------------------------------------------------------------ |
| `url`         | string         | New HTTPS destination URL. Max 2048 characters. Private/internal IPs and localhost are rejected. |
| `events`      | array          | New event subscription set. Must be non-empty if present. Replaces the existing set entirely.    |
| `description` | string \| null | Human-readable label. Max 255 characters.                                                        |

### Response (200)

Same shape as [Create a webhook endpoint](#create-a-webhook-endpoint), minus `secret`. Returns `422 VALIDATION_FAILED` if no field is provided, or if the resulting `(url, events)` pair collides with another endpoint on the same app. Endpoints belonging to a different org return `404`.

***

## Rotate the signing secret

```
POST https://api.usehasp.com/v1/webhooks/{endpoint_id}/rotate-secret
Authorization: Bearer hasp_api_live_...
```

Mints a new signing secret. The previous secret remains valid for signing for **7 days** — every delivery during that window carries signatures for both secrets (see [Verifying signatures](/app-builder/reference/outbound-webhooks#verifying-signatures)), so you can deploy the new secret without missing or rejecting a delivery.

### Response (200)

```json theme={null}
{
  "success": true,
  "data": {
    "id": "01JQWH0000000000000000000",
    "app_id": "01JQAPP00000000000000000",
    "url": "https://example.com/hooks/hasp",
    "events": ["record.created", "record.updated"],
    "is_active": true,
    "description": "Production record sync",
    "created_at": "2026-05-02T10:00:00+00:00",
    "secret": "hasp_obh_live_b7e1f4a2...",
    "previous_secret_valid_until": "2026-05-09T10:00:00+00:00"
  }
}
```

`data.secret` is the new plaintext secret — shown once, same as at creation. `data.previous_secret_valid_until` is when the old secret stops being accepted.

***

## List deliveries

```
GET https://api.usehasp.com/v1/webhooks/{endpoint_id}/deliveries
Authorization: Bearer hasp_api_live_...
```

Cursor-paginated delivery history for one endpoint, newest first. Use this to discover the delivery `id` values needed by `POST /v1/webhooks/deliveries/bulk-replay`.

### Response

```json theme={null}
{
  "data": [
    {
      "id": "whd_01JQDELIVERY0000000000000",
      "object": "webhook_delivery",
      "webhook_endpoint_id": "whep_01JQWH0000000000000000000",
      "event": "record.created",
      "response_status": 200,
      "attempts": 1,
      "completed_at": "2026-05-02T10:00:05+00:00",
      "error_message": null,
      "created_at": "2026-05-02T10:00:00+00:00"
    }
  ],
  "meta": { "next_cursor": null, "has_more": false }
}
```

***

## List event types

```
GET https://api.usehasp.com/v1/webhooks/event-types
Authorization: Bearer hasp_api_live_...
```

Returns the catalog of event strings a webhook endpoint may subscribe to — the valid values for `events` on [create](#create-a-webhook-endpoint) and [update](#update-a-webhook-endpoint).

| Event                   | Description                               |
| ----------------------- | ----------------------------------------- |
| `record.created`        | A single record is created.               |
| `record.updated`        | A record is updated.                      |
| `record.deleted`        | A record is deleted.                      |
| `record.bulk_created`   | Records are created via a bulk operation. |
| `record.bulk_updated`   | Records are updated via a bulk operation. |
| `record.bulk_deleted`   | Records are deleted via a bulk operation. |
| `schema.updated`        | The entity schema is modified.            |
| `app_version.published` | A new app version is published.           |

### Response

```json theme={null}
{
  "data": [
    { "object": "webhook_event_type", "event": "record.created", "description": "A single record is created." },
    { "object": "webhook_event_type", "event": "record.updated", "description": "A record is updated." }
  ],
  "meta": { "next_cursor": null, "has_more": false }
}
```

***

## Delete a webhook endpoint

```
DELETE https://api.usehasp.com/v1/webhooks/{endpoint_id}
Authorization: Bearer hasp_api_live_...
```

Soft-deletes the endpoint. In-flight deliveries that are already queued will still attempt delivery; new events will not be dispatched.

Endpoints belonging to a different org return `404`.

### Response

```json theme={null}
{
  "success": true,
  "data": {
    "id": "01JQWH0000000000000000000"
  }
}
```

***

## Error codes

| Code                 | HTTP | Description                                            |
| -------------------- | ---- | ------------------------------------------------------ |
| `INVALID_API_KEY`    | 401  | Caller's key is invalid or revoked                     |
| `MISSING_SCOPE`      | 403  | Key lacks `control:webhooks` scope                     |
| `VALIDATION_FAILED`  | 422  | Invalid URL, empty events array, or unknown event type |
| `RESOURCE_NOT_FOUND` | 404  | Endpoint ID not found or app belongs to another org    |
