Limai Docs
API ReferenceWebhooks

Webhook Management API

Create, configure, verify and monitor webhook subscriptions over the v1 API.

Everything the Events page in the app does to a webhook subscription is available over the v1 API: create and update subscriptions, verify the endpoint, pause and resume delivery, rotate the signing secret, read the delivery log, read aggregated metrics, and re-queue failed deliveries.

For the payload shapes your endpoint receives, and for signature verification, see Webhook Setup. For the same operations from a terminal, see the limai webhooks command group.

Base Path and Authentication

Every route lives under the project that owns the subscription:

https://app.limai.io/api/v1/projects/{projectId}/webhooks

All routes take a Bearer token:

Authorization: Bearer YOUR_API_TOKEN

An API token inherits the role of the user who created it, and every webhook route -- including the read-only ones -- requires the EDIT_MODELS permission. That means OWNER and DEVELOPER only. A token belonging to a USER gets 403 on GET just as it does on DELETE; there is no read-only tier for webhooks. A project-scoped token can only reach its own project.

The Webhook Object

Response200
{
  "id": "whk_abc123",
  "projectId": "proj_abc123",
  "url": "https://example.com/hooks/limai",
  "events": [
    "DOCUMENT_EXTRACTED",
    "DOCUMENT_EXTRACTION_FAILED"
  ],
  "isActive": true,
  "isVerified": false,
  "createdAt": "2026-08-26T09:00:00.000Z",
  "updatedAt": "2026-08-26T09:00:00.000Z",
  "deployments": [
    {
      "id": "dep_123abc",
      "name": "Invoices",
      "type": "MODEL"
    }
  ],
  "agents": [],
  "classifiers": [],
  "splitters": []
}
FieldTypeDescription
idstringSubscription identifier. Used as {webhookId} in every other route.
projectIdstringThe project that owns the subscription.
urlstringThe HTTPS endpoint deliveries are POSTed to.
eventsstring[]The subscribed event types. See Event Types.
isActivebooleanWhether the subscription is enabled. Toggled by pause / resume or by isActive on update.
isVerifiedbooleanWhether the endpoint has passed the verification challenge.
createdAtISO 8601Creation time.
updatedAtISO 8601Last modification time.
deploymentsobject[]Deployment routes, each with id, name and a type of MODEL or PRODUCTION.
agentsobject[]Agent routes, each with id and name.
classifiersobject[]Classifier routes, each with id and name.
splittersobject[]Splitter routes, each with id and name.

A subscription only receives deliveries when isActive and isVerified are both true. Creating a subscription sets isVerified to false; so does changing its url, and so does rotating its secret. After any of those, verify again or nothing will be delivered.

secretKey is not part of this object. It is returned exactly twice in a subscription's life: on create and on rotate secret. Store it when you see it -- no route reads it back.

Routes and Route IDs

A subscription is routed by the deployments, agents, classifiers and splitters it listens to. Which family a given event needs is described in Routing Families; the API enforces the same rule, so a subscription that selects a document event without a deployment route is rejected with 400.

Route ID arrays behave the same way everywhere:

  • Each array holds at most 100 IDs, and the IDs within one array must be unique.
  • Every ID must belong to this project. Classifiers are the one exception: a global classifier in the same organization is accepted.
  • On update, a supplied array replaces that family's routes wholesale. Omitting the array leaves that family untouched; sending an empty array clears it.

List Webhooks

GET/api/v1/projects/{projectId}/webhooks

Returns every webhook subscription in the project, newest first.

Parameters

NameTypeInRequiredDescription
projectIdstringpathYesThe project ID

Request

const res = await fetch(
"https://app.limai.io/api/v1/projects/proj_abc123/webhooks",
{
  headers: {
    Authorization: "Bearer YOUR_API_TOKEN",
  },
}
);
const { webhooks } = await res.json();

Response

Response200
{
  "webhooks": [
    {
      "id": "whk_abc123",
      "projectId": "proj_abc123",
      "url": "https://example.com/hooks/limai",
      "events": [
        "DOCUMENT_EXTRACTED"
      ],
      "isActive": true,
      "isVerified": true,
      "createdAt": "2026-08-26T09:00:00.000Z",
      "updatedAt": "2026-08-26T09:04:00.000Z",
      "deployments": [
        {
          "id": "dep_123abc",
          "name": "Invoices",
          "type": "MODEL"
        }
      ],
      "agents": [],
      "classifiers": [],
      "splitters": []
    }
  ]
}

Create a Webhook

POST/api/v1/projects/{projectId}/webhooks

Creates a subscription and returns it with its signing secret. This is one of only two responses that ever carries secretKey.

Every field is optional, which lets you create a draft and fill it in later. The subscription is created with isActive: true only when it is fully configured -- a URL, at least one event, and at least one route. Anything less is created inactive. isVerified is always false on create.

Parameters

NameTypeInRequiredDescription
projectIdstringpathYesThe project ID
urlstringbodyNoHTTPS endpoint to deliver to. Must be HTTPS and publicly resolvable
eventsstring[]bodyNoEvent types to subscribe to
deploymentIdsstring[]bodyNoDeployment routes. Max 100, unique, must belong to this project
agentIdsstring[]bodyNoAgent routes. Max 100, unique, must belong to this project
classifierIdsstring[]bodyNoClassifier routes. Max 100, unique. Project classifiers and global classifiers in the same organization
splitterIdsstring[]bodyNoSplitter routes. Max 100, unique, must belong to this project

Request

const res = await fetch(
"https://app.limai.io/api/v1/projects/proj_abc123/webhooks",
{
  method: "POST",
  headers: {
    Authorization: "Bearer YOUR_API_TOKEN",
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
    url: "https://example.com/hooks/limai",
    events: ["DOCUMENT_EXTRACTED", "DOCUMENT_EXTRACTION_FAILED"],
    deploymentIds: ["dep_123abc"],
  }),
}
);
const webhook = await res.json();
storeSecret(webhook.secretKey);

Response

Response201
{
  "id": "whk_abc123",
  "projectId": "proj_abc123",
  "url": "https://example.com/hooks/limai",
  "events": [
    "DOCUMENT_EXTRACTED",
    "DOCUMENT_EXTRACTION_FAILED"
  ],
  "isActive": true,
  "isVerified": false,
  "createdAt": "2026-08-26T09:00:00.000Z",
  "updatedAt": "2026-08-26T09:00:00.000Z",
  "deployments": [
    {
      "id": "dep_123abc",
      "name": "Invoices",
      "type": "MODEL"
    }
  ],
  "agents": [],
  "classifiers": [],
  "splitters": [],
  "secretKey": "9f2c4b6d8e0a1c3e5f7a9b1d3f5a7c9e1b3d5f7a9c1e3f5a7b9d1f3a5c7e9b1d"
}

Errors

StatusWhen
400URL is not HTTPS, is unparseable, or resolves to a blocked address; a route ID does not belong to the project; a route array exceeds 100 IDs or repeats one; an event family has no route of its kind
403The token's role is not OWNER or DEVELOPER, or the token cannot reach this project
404The project does not exist or is not visible to the token

Get a Webhook

GET/api/v1/projects/{projectId}/webhooks/{webhookId}

Returns one subscription. No secret.

Parameters

NameTypeInRequiredDescription
projectIdstringpathYesThe project ID
webhookIdstringpathYesThe webhook subscription ID

Request

curl "https://app.limai.io/api/v1/projects/proj_abc123/webhooks/whk_abc123" \
  -H "Authorization: Bearer YOUR_API_TOKEN"

Response

Response200
{
  "id": "whk_abc123",
  "projectId": "proj_abc123",
  "url": "https://example.com/hooks/limai",
  "events": [
    "DOCUMENT_EXTRACTED"
  ],
  "isActive": true,
  "isVerified": true,
  "createdAt": "2026-08-26T09:00:00.000Z",
  "updatedAt": "2026-08-26T09:04:00.000Z",
  "deployments": [
    {
      "id": "dep_123abc",
      "name": "Invoices",
      "type": "MODEL"
    }
  ],
  "agents": [],
  "classifiers": [],
  "splitters": []
}

Update a Webhook

PATCH/api/v1/projects/{projectId}/webhooks/{webhookId}

Updates the fields you send and leaves the rest alone. At least one field is required -- an empty body is a 400.

Changing url sets isVerified back to false. Deliveries stop until you verify the new URL.

Parameters

NameTypeInRequiredDescription
projectIdstringpathYesThe project ID
webhookIdstringpathYesThe webhook subscription ID
urlstringbodyNoNew HTTPS endpoint. Resets isVerified to false
eventsstring[]bodyNoReplaces the subscribed event types
isActivebooleanbodyNoEnable or disable delivery. Same effect as pause / resume
deploymentIdsstring[]bodyNoReplaces the deployment routes. Max 100, unique
agentIdsstring[]bodyNoReplaces the agent routes. Max 100, unique
classifierIdsstring[]bodyNoReplaces the classifier routes. Max 100, unique
splitterIdsstring[]bodyNoReplaces the splitter routes. Max 100, unique

Request

await fetch(
"https://app.limai.io/api/v1/projects/proj_abc123/webhooks/whk_abc123",
{
  method: "PATCH",
  headers: {
    Authorization: "Bearer YOUR_API_TOKEN",
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
    events: ["DOCUMENT_EXTRACTED", "DOCUMENT_VALIDATED"],
    deploymentIds: ["dep_123abc", "dep_456def"],
  }),
}
);

Response

Response200
{
  "id": "whk_abc123",
  "projectId": "proj_abc123",
  "url": "https://example.com/hooks/limai",
  "events": [
    "DOCUMENT_EXTRACTED",
    "DOCUMENT_VALIDATED"
  ],
  "isActive": true,
  "isVerified": true,
  "createdAt": "2026-08-26T09:00:00.000Z",
  "updatedAt": "2026-08-26T10:15:00.000Z",
  "deployments": [
    {
      "id": "dep_123abc",
      "name": "Invoices",
      "type": "MODEL"
    },
    {
      "id": "dep_456def",
      "name": "Receipts",
      "type": "PRODUCTION"
    }
  ],
  "agents": [],
  "classifiers": [],
  "splitters": []
}

Delete a Webhook

DELETE/api/v1/projects/{projectId}/webhooks/{webhookId}

Deletes the subscription and its routes. Returns 204 with no body. This is not reversible; to stop delivery without losing the configuration, use pause.

Request

curl -X DELETE "https://app.limai.io/api/v1/projects/proj_abc123/webhooks/whk_abc123" \
  -H "Authorization: Bearer YOUR_API_TOKEN"

Response

204 No Content

Pause a Webhook

POST/api/v1/projects/{projectId}/webhooks/{webhookId}/pause

Sets isActive to false. Routes, events, secret and verification state are untouched, so resuming needs no re-verification. Returns the updated Webhook object.

Request

curl -X POST "https://app.limai.io/api/v1/projects/proj_abc123/webhooks/whk_abc123/pause" \
  -H "Authorization: Bearer YOUR_API_TOKEN"

Response

Response200
{
  "id": "whk_abc123",
  "projectId": "proj_abc123",
  "url": "https://example.com/hooks/limai",
  "events": [
    "DOCUMENT_EXTRACTED"
  ],
  "isActive": false,
  "isVerified": true,
  "createdAt": "2026-08-26T09:00:00.000Z",
  "updatedAt": "2026-08-26T11:00:00.000Z",
  "deployments": [
    {
      "id": "dep_123abc",
      "name": "Invoices",
      "type": "MODEL"
    }
  ],
  "agents": [],
  "classifiers": [],
  "splitters": []
}

Resume a Webhook

POST/api/v1/projects/{projectId}/webhooks/{webhookId}/resume

Sets isActive to true. Delivery only actually restarts if the subscription is also verified.

Request

curl -X POST "https://app.limai.io/api/v1/projects/proj_abc123/webhooks/whk_abc123/resume" \
  -H "Authorization: Bearer YOUR_API_TOKEN"

Response

Response200
{
  "id": "whk_abc123",
  "projectId": "proj_abc123",
  "url": "https://example.com/hooks/limai",
  "events": [
    "DOCUMENT_EXTRACTED"
  ],
  "isActive": true,
  "isVerified": true,
  "createdAt": "2026-08-26T09:00:00.000Z",
  "updatedAt": "2026-08-26T11:05:00.000Z",
  "deployments": [
    {
      "id": "dep_123abc",
      "name": "Invoices",
      "type": "MODEL"
    }
  ],
  "agents": [],
  "classifiers": [],
  "splitters": []
}

Verify a Webhook

POST/api/v1/projects/{projectId}/webhooks/{webhookId}/verify

Sends a signed VERIFICATION event to the subscription's URL. Your endpoint must echo the challenge value back in its response body. On success isVerified becomes true; on failure it is left alone.

This route's response shape differs from the rest: it is success plus message, not a Webhook object.

Verification pings are not written to the delivery log. They never appear in List Deliveries.

Request

curl -X POST "https://app.limai.io/api/v1/projects/proj_abc123/webhooks/whk_abc123/verify" \
  -H "Authorization: Bearer YOUR_API_TOKEN"

Response

Response200
{
  "success": true,
  "message": "Webhook URL verified successfully"
}

Endpoint Unreachable

When your endpoint refuses the challenge, times out, or answers with the wrong body, the route answers 502 with the same two fields:

Response502
{
  "success": false,
  "message": "Webhook endpoint responded with status 500"
}

A 400 here means something else: the subscription's URL or secret changed while the challenge was in flight (Webhook URL or secret changed during verification; verify again). Re-read the subscription and verify again.


Rotate the Signing Secret

POST/api/v1/projects/{projectId}/webhooks/{webhookId}/rotate-secret

Generates a new signing secret and returns the subscription with secretKey. Every event enqueued after the rotation is signed with the new secret.

Rotation does not reach into the delivery queue. A job captures the URL and secret at enqueue time and keeps them, so deliveries that were already queued or mid-retry when you rotated are still signed with the old secret -- and so is any retry of a delivery enqueued before the rotation. Automatic retries drain within a few minutes (five attempts, 10 s exponential backoff), but a retained payload can be retried by hand for about 7 days. Keep accepting the old secret alongside the new one for as long as you may retry such deliveries, rather than dropping it the moment you rotate.

Rotation also resets isVerified to false, because the challenge is signed with the secret. Verify again afterwards or delivery stays stopped.

Request

curl -X POST "https://app.limai.io/api/v1/projects/proj_abc123/webhooks/whk_abc123/rotate-secret" \
  -H "Authorization: Bearer YOUR_API_TOKEN"

Response

Response200
{
  "id": "whk_abc123",
  "projectId": "proj_abc123",
  "url": "https://example.com/hooks/limai",
  "events": [
    "DOCUMENT_EXTRACTED"
  ],
  "isActive": true,
  "isVerified": false,
  "createdAt": "2026-08-26T09:00:00.000Z",
  "updatedAt": "2026-08-26T12:00:00.000Z",
  "deployments": [
    {
      "id": "dep_123abc",
      "name": "Invoices",
      "type": "MODEL"
    }
  ],
  "agents": [],
  "classifiers": [],
  "splitters": [],
  "secretKey": "1a3c5e7b9d1f3a5c7e9b1d3f5a7c9e1b3d5f7a9c1e3f5a7b9d1f3a5c7e9b1d3f"
}

List Deliveries

GET/api/v1/projects/{projectId}/webhooks/{webhookId}/deliveries

Returns the delivery log for one subscription, newest first, with a total for pagination.

Parameters

NameTypeInRequiredDescription
projectIdstringpathYesThe project ID
webhookIdstringpathYesThe webhook subscription ID
statusstringqueryNoOne of success, failed, dead_lettered, retrying, pending
eventTypestringqueryNoFilter to a single event type
deploymentIdstringqueryNoOnly deliveries routed by this deployment
agentIdstringqueryNoOnly deliveries routed by this agent
classifierIdstringqueryNoOnly deliveries routed by this classifier
splitterIdstringqueryNoOnly deliveries routed by this splitter
limitintegerqueryNoPage size, 1 to 200(default: 50)
offsetintegerqueryNoRows to skip(default: 0)

Request

const params = new URLSearchParams({ status: "failed", limit: "50" });
const res = await fetch(
`https://app.limai.io/api/v1/projects/proj_abc123/webhooks/whk_abc123/deliveries?${params}`,
{
  headers: {
    Authorization: "Bearer YOUR_API_TOKEN",
  },
}
);
const { deliveries, total } = await res.json();

Response

Response200
{
  "deliveries": [
    {
      "eventId": "evt_1756199524000_k3f9x2mq7ab",
      "subscriptionId": "whk_abc123",
      "eventType": "DOCUMENT_EXTRACTED",
      "timestamp": "2026-08-26T09:12:04.000Z",
      "url": "https://example.com/hooks/limai",
      "status": "failed",
      "retryCount": 4,
      "routeFamily": "deployment",
      "responseCode": 500,
      "responseBody": "internal error",
      "errorType": "ENDPOINT_ERROR",
      "durationMs": 812,
      "deploymentId": "dep_123abc",
      "deploymentName": "Invoices"
    }
  ],
  "total": 137,
  "limit": 50,
  "offset": 0
}

The Delivery Object

FieldTypeDescription
eventIdstringThe event identifier, the same one your endpoint received. Use it with retry.
subscriptionIdstringThe subscription this delivery belongs to.
eventTypestringThe event type that was delivered.
timestampISO 8601When the event happened, not when the attempt was made.
urlstringThe URL the attempt was made against, as it was at the time.
statusstringOne of the five statuses below.
retryCountintegerAttempts after the first. A delivery that succeeded first try reads 0.
routeFamilystringdeployment, agent, classifier or splitter. Absent when the delivery carried no route.
responseCodeintegerHTTP status your endpoint returned. Absent for transport errors and blocked URLs.
responseBodystringFirst 500 characters of your endpoint's response body, when there was one.
errorTypestringENDPOINT_REJECTED, ENDPOINT_ERROR, TRANSPORT_ERROR, URL_BLOCKED, TERMINAL_REJECTION or RETRIES_EXHAUSTED. Absent on success.
durationMsintegerHow long the attempt took.
deploymentId, deploymentNamestringPresent when routeFamily is deployment.
agentId, agentNamestringPresent when routeFamily is agent.
classifierId, classifierNamestringPresent when routeFamily is classifier.
splitterId, splitterNamestringPresent when routeFamily is splitter.

Optional fields are omitted, not set to null.

Delivery Statuses

StatusMeaning
successYour endpoint answered 2xx. Terminal.
pendingQueued, not attempted yet.
retryingAttempted at least once, failed, and a further attempt is scheduled.
failedNo further attempt will be made automatically -- either the failure was terminal (a 4xx, or a blocked URL) or all 5 attempts were spent. Retryable by hand.
dead_letteredThe job reached the dead-letter queue. Retryable by hand while the payload is still retained.

failed and dead_lettered describe the same delivery seen from two sides: the worker marks the last attempt failed, then the dead-letter hook marks the job dead_lettered. Both count as failures in metrics, and both are candidates for retry.

The log only holds events enqueued from 2026-08-26 onwards, when per-attempt recording shipped. See What the Delivery Log Does Not Tell You.


Get Metrics

GET/api/v1/projects/{projectId}/webhooks/{webhookId}/metrics

Aggregated delivery counts for one subscription over a time window, broken down per route and bucketed into a time series.

Parameters

NameTypeInRequiredDescription
projectIdstringpathYesThe project ID
webhookIdstringpathYesThe webhook subscription ID
windowstringqueryNoTime window: 24h, 7d or 30d(default: 30d)
bucketstringqueryNoSeries granularity: hour or day. Defaults to hour for a 24h window and day otherwise

Request

curl "https://app.limai.io/api/v1/projects/proj_abc123/webhooks/whk_abc123/metrics?window=7d&bucket=day" \
  -H "Authorization: Bearer YOUR_API_TOKEN"

Response

Response200
{
  "organizationId": "org_xyz789",
  "totalEvents": 420,
  "successfulEvents": 402,
  "failedEvents": 15,
  "pendingEvents": 3,
  "successRate": 95.71,
  "averageResponseTime": 214.5,
  "perRoute": [
    {
      "routeFamily": "deployment",
      "routeId": "dep_123abc",
      "routeName": "Invoices",
      "totalEvents": 380,
      "successfulEvents": 370,
      "failedEvents": 8,
      "pendingEvents": 2,
      "successRate": 97.37
    },
    {
      "routeFamily": "classifier",
      "routeId": "cls_456def",
      "routeName": "Inbound router",
      "totalEvents": 40,
      "successfulEvents": 32,
      "failedEvents": 7,
      "pendingEvents": 1,
      "successRate": 80
    }
  ],
  "series": [
    {
      "bucket": "2026-08-25T00:00:00.000Z",
      "successful": 210,
      "failed": 4,
      "pending": 0
    },
    {
      "bucket": "2026-08-26T00:00:00.000Z",
      "successful": 192,
      "failed": 11,
      "pending": 3
    }
  ],
  "window": "7d",
  "bucket": "day"
}
FieldTypeDescription
organizationIdstringThe organization the project belongs to.
totalEventsintegerDeliveries recorded in the window.
successfulEventsintegerDeliveries with status success.
failedEventsintegerfailed and dead_lettered together.
pendingEventsintegerpending and retrying together.
successRatenumberPercentage, 0 to 100. 0 when there were no deliveries.
averageResponseTimenumberMean durationMs across successful deliveries. Omitted when nothing succeeded.
perRouteobject[]One entry per route that saw traffic, busiest first.
seriesobject[]Contiguous buckets covering the whole window -- quiet buckets are present with zeros.
windowstringThe window actually applied.
bucketstringThe granularity actually applied.

Each perRoute entry carries routeFamily (deployment, agent, classifier or splitter), routeId, routeName, its own totalEvents, successfulEvents, failedEvents and pendingEvents, and a successRate. routeName falls back to the ID if the route has since been deleted.

perRoute replaces the older perDeployment breakdown. Classification and splitting events have no deployment to key off, so a deployment-only breakdown could not represent them.


Retry Failed Deliveries

POST/api/v1/projects/{projectId}/webhooks/{webhookId}/retry

Re-queues deliveries that ended failed or dead_lettered. A retry replays the job as it was enqueued: the delivery goes to the URL and is signed with the secret captured at enqueue time, not the subscription's current ones. Changing the URL or rotating the secret does not redirect or re-sign a retried delivery -- re-drive the event from your own side if you need that.

Parameters

NameTypeInRequiredDescription
projectIdstringpathYesThe project ID
webhookIdstringpathYesThe webhook subscription ID
eventIdsstring[]bodyYesEvent IDs to retry. Between 1 and 100

Request

const res = await fetch(
"https://app.limai.io/api/v1/projects/proj_abc123/webhooks/whk_abc123/retry",
{
  method: "POST",
  headers: {
    Authorization: "Bearer YOUR_API_TOKEN",
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
    eventIds: ["evt_1756199524000_k3f9x2mq7ab"],
  }),
}
);
const { retriedCount, skipped } = await res.json();

Response

Response200
{
  "retriedCount": 1,
  "retried": [
    {
      "eventId": "evt_1756199524000_k3f9x2mq7ab",
      "url": "https://example.com/hooks/limai"
    }
  ],
  "skipped": [
    {
      "eventId": "evt_1756199111000_p8q2r4s6t8u",
      "url": "https://example.com/hooks/limai",
      "reason": "payload_unavailable"
    }
  ],
  "message": "Re-queued 1 of 2 webhook deliveries; the rest are already in flight, or their original payload is no longer retained"
}

A 200 here means the request reached the delivery queue, not that anything was re-sent. Whether a given delivery actually went back on the queue is reported per delivery: everything in retried was re-queued, everything in skipped was not, with a reason. A 500 means the queue itself could not be reached.

reasonMeaning
already_deliveredThe queued job completed successfully. There is nothing to re-send.
in_flightThe job is waiting, active or delayed right now. A retry would duplicate it.
payload_unavailableThe original payload is no longer retained, so the delivery cannot be reconstructed. See below.

Asking to retry an event ID that is not a failed delivery of this subscription is not an error either -- it is simply not among the candidates, and the response reads No failed webhook deliveries to retry.

How Far Back Retry Reaches

Retry replays the queued job's retained payload, not a copy in the database. The delivery log therefore outlives the ability to retry from it: a row can read failed long after its payload is gone.

  • A failed delivery is retained on the delivery queue for 7 days, and only the most recent 5000 failures are kept. Whichever limit is hit first wins, so a high-volume project loses reach sooner than 7 days.
  • A dead-lettered delivery is retained on the dead-letter queue, which is swept on the same 7 day retention. The dead-letter scan also stops after 5000 entries per queue state.

Past that, the delivery still appears in the log, but retry answers payload_unavailable. Re-drive it from your own side using the eventId and the source data instead.

Note that retriedCount counts deliveries put back on the queue, not deliveries that ultimately succeed. A re-queued delivery also keeps its attempt counter rather than starting a fresh set of five, so one that had already exhausted its attempts gets a single further attempt before it dead-letters again. See Retry Policy.


Error Responses

Every route on this page shares one error shape, an object with a single error string. The exception is verify, which reports endpoint failures as success: false plus a message, with status 502.

StatusBodyCause
400errorA domain rule was broken -- bad URL, a route that is not in this project, an event family with no route
400error plus detailsThe request body or query string failed schema validation. details carries the flattened field errors
401Authentication requiredMissing or unrecognised API token
403Unauthorized: Only OWNER and DEVELOPER roles can perform this operationThe token's role lacks EDIT_MODELS
403Unauthorized: No access to this projectThe token cannot reach this project
404Webhook not foundThe subscription, or the project, does not exist for this token
502success: false plus messageVerify only: your endpoint refused the challenge, timed out, or answered with the wrong body
500Internal server errorUnexpected failure

404 reads Webhook not found for a missing project too -- it does not distinguish the two.

On this page