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

# Webhooks API

> Create and manage webhook subscriptions from code or an app: the event catalog, scope rules, pausing and resuming, and the delivery log.

Everything you can do under **Settings → Integrations → Webhooks** is available here, for API keys and for [OAuth apps](https://docs.useorbitcrm.ai/apps/webhooks.md). Payloads, headers and retries are described in the [webhooks guide](https://docs.useorbitcrm.ai/developers/webhooks/overview.md).

## Scopes

| Scope | Allows |
| - | - |
| `webhooks:read` | List webhooks, read one, read its deliveries, read the event catalog |
| `webhooks:write` | Create, change and delete webhooks |
| The object's read scope | Subscribing to an object's events: `submissions:read`, `contacts:read`, `companies:read`, `deals:read`, `tasks:read`, `meetings:read` or `dialer:read` |

A request that subscribes to an event whose read scope the credential lacks gets `403 insufficient_scope` with `required_scopes` naming what is missing.

## Endpoints

| Method | Endpoint | Description |
| - | - | - |
| `GET` | `/api/v1/webhooks/events` | The event catalog, grouped by object, with `subscribable` per group for this credential |
| `GET` | `/api/v1/webhooks` | Your webhooks (an app sees the ones it registered in this workspace; an API key the workspace's own) |
| `POST` | `/api/v1/webhooks` | Create a webhook. The signing secret is returned once |
| `GET` | `/api/v1/webhooks/:id` | One webhook |
| `PATCH` | `/api/v1/webhooks/:id` | Change `url`, `events`, `form_id`, `filters` or `is_active` |
| `POST` | `/api/v1/webhooks/:id/rotate-secret` | A new signing secret, returned once; the old one keeps signing for 24 hours |
| `DELETE` | `/api/v1/webhooks/:id` | Delete a webhook |
| `GET` | `/api/v1/webhooks/:id/deliveries` | Delivery attempts, newest first |

## Create a webhook

```bash theme={null}
curl -X POST https://app.useorbitcrm.ai/api/v1/webhooks \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: 4d3c2b1a-deal-sync" \
  -d '{
    "url": "https://partner.example/orbit/events",
    "events": ["deal.created", "deal.stage_changed", "contact.created", "call.analyzed"]
  }'
```

```json theme={null}
{
  "data": {
    "id": "f0e1d2c3-...",
    "url": "https://partner.example/orbit/events",
    "events": ["deal.created", "deal.stage_changed", "contact.created", "call.analyzed"],
    "is_active": true,
    "form_id": null,
    "created_at": "2026-10-27T14:00:00Z",
    "secret": "a1b2c3...64 hex characters"
  },
  "message": "Webhook created. Save the secret - it will only be shown once."
}
```

| Field | Type | Notes |
| - | - | - |
| `url` | string | HTTPS. Private and internal addresses are refused, at creation and again on every delivery |
| `events` | string\[] | One or more events from the catalog. Duplicates are dropped |
| `form_id` | uuid | Optional. Limits the webhook to one form's submission events. Refused when `events` contains anything other than `submission.*` events, which are workspace-wide |
| `filters` | object | Optional. `{ "pipeline_ids": [...], "stage_ids": [...] }` (up to 50 ids each, the workspace's own). Deal events are delivered only for deals that are in one of the named pipelines and stages at the time of the event, and quote events only for quotes whose deal is; other events are not filtered. Needs at least one `deal.*` or `quote.*` event. Removing the last of them from a webhook clears its filters. |

Send an `Idempotency-Key` header to make retries of the create call safe; a replay returns the webhook without the secret.

Limits: 5 webhooks per workspace for an app in development, 100 once the app is approved; 500 per workspace in total. Creation is rate limited to 60 an hour per app per workspace in development, 500 once approved, and 60 an hour per workspace for API keys; the create response and a `429` carry `RateLimit-Limit`, `RateLimit-Remaining` and, on a `429`, `RateLimit-Reset` and `Retry-After`. To switch a webhook off and on, `PATCH` `is_active` rather than deleting and re-creating it.

The URL's host is resolved when you create or change a webhook, and refused only when it points at a private or internal address. A host that does not resolve yet is accepted; every delivery resolves it again, and a wrong URL shows in the delivery log.

## Change a webhook

```bash theme={null}
curl -X PATCH https://app.useorbitcrm.ai/api/v1/webhooks/f0e1d2c3-... \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{ "events": ["deal.created", "deal.stage_changed", "deal.deleted"], "is_active": true }'
```

Setting `is_active` to `true` on a webhook that was switched off after a failure streak clears `consecutive_failures` and `last_error`. Changing `form_id` re-checks the current events against the new filter.

## The delivery log

```bash theme={null}
curl "https://app.useorbitcrm.ai/api/v1/webhooks/f0e1d2c3-.../deliveries?status=failed&per_page=20" \
  -H "Authorization: Bearer $TOKEN"
```

```json theme={null}
{
  "data": [
    {
      "id": "0a1b2c3d-...",
      "event": "deal.stage_changed",
      "event_id": "5f0c1f2e-9c0a-5a3b-8d1e-6b2f0c9a4d11",
      "object_id": "9a1d3c4e-...",
      "success": false,
      "status_code": 503,
      "error_message": "HTTP 503: Service Unavailable",
      "attempt_number": 2,
      "is_retry": true,
      "attempted_at": "2026-10-27T14:32:10Z",
      "completed_at": "2026-10-27T14:32:11Z",
      "duration_ms": 812
    }
  ],
  "meta": { "total": 3, "page": 1, "per_page": 20, "total_pages": 1 }
}
```

Query parameters: `page`, `per_page` (max 100), `event` (one catalog event), `status` (`success` or `failed`). Payload data is not kept in the log; `event_id` and `object_id` let you look the record up.

## The webhook object

| Field | Meaning |
| - | - |
| `id`, `url`, `events`, `form_id`, `filters`, `is_active` | What you set |
| `last_triggered_at`, `last_success_at`, `last_error` | The latest attempt, the latest success, and why the latest failure failed |
| `consecutive_failures` | Events in a row that failed every attempt; at 10 the webhook is switched off |
| `created_at`, `updated_at` | Timestamps |

The secret is only returned by `POST`. Store it; it is what you [verify signatures](https://docs.useorbitcrm.ai/developers/webhooks/security.md) with. To replace it, `POST /api/v1/webhooks/:id/rotate-secret`: the response carries the new secret and `previous_secret_expires_at`, and until then every delivery carries `X-Orbit-Signature-Previous` (signed with the old secret) next to `X-Orbit-Signature`, so a receiver can verify with either while it switches.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.