The WebhookEndpoint object
| Field | Type | Notes |
|---|---|---|
id | string (UUID) | Stable identifier. |
url | string | The HTTPS URL we deliver events to. |
description | string | null | Free-form, max 500 characters. Useful for distinguishing endpoints in the UI. |
events | string[] | The event types this endpoint receives. See event types. |
is_active | boolean | If false, deliveries are skipped. Set to false automatically after too many failures. |
verified_at | string (ISO 8601) | null | When we last successfully ping-verified the URL. |
last_success_at | string (ISO 8601) | null | Most recent 2xx delivery. |
last_failure_at | string (ISO 8601) | null | Most recent failed delivery attempt. |
consecutive_failures | integer | Reset to 0 on any successful delivery. |
disabled_at | string (ISO 8601) | null | Set when we auto-disable after consecutive failures. |
created_at | string (ISO 8601) | UTC timestamp. |
updated_at | string (ISO 8601) | UTC timestamp of the last change. |
Event types
| Event | Fires when |
|---|---|
upload.created | An upload row is inserted (both API and web sources). |
upload.completed | Processing finishes successfully. Payload includes signed download URLs. |
upload.failed | Processing fails. Payload includes error_message. |
events when creating an endpoint, you get the default subset ["upload.completed", "upload.failed"]. To receive upload.created you must list it explicitly.
Create a webhook endpoint
2xx to a synchronous ping challenge before we’ll save it — this catches typos and unreachable URLs at registration time, not at first delivery.
Request body
| Field | Type | Required | Notes |
|---|---|---|---|
url | string (HTTPS URL) | yes | Must be https:// and resolve to a public IP. See URL safety rules. |
description | string | no | Max 500 characters. |
events | string[] | no | Defaults to ["upload.completed", "upload.failed"]. |
- cURL
- Node.js
- Python
Response
secret is a 64-character hex string (32 bytes of entropy). It is returned only on creation — store it next to the API key. We use it to compute the HMAC signature on every event sent to this endpoint, and your receiver uses it to verify those signatures.
Errors
| Status | When |
|---|---|
400 | URL is not HTTPS, resolves to a private IP, or didn’t respond 2xx to the ping challenge. The body includes error: "ping_failed" plus the receiver’s status code if one came back. |
401 | Auth failure |
429 | Rate limit hit |
List webhook endpoints
- cURL
- Node.js
- Python
Response
A JSON array ofWebhookEndpoint objects.
Retrieve a webhook endpoint
- cURL
- Node.js
- Python
Errors
| Status | When |
|---|---|
401 | Auth failure |
404 | Endpoint doesn’t exist or belongs to another organization |
Update a webhook endpoint
Request body
| Field | Type | Notes |
|---|---|---|
url | string (HTTPS URL) | If changed, the new URL is ping-verified synchronously with the existing secret. The update fails if verification fails. |
description | string | Max 500 characters. |
events | string[] | Replaces the current event list (no merge). |
is_active | boolean | Set to true to re-enable a manually disabled or auto-disabled endpoint. Re-enabling resets consecutive_failures to 0 and clears disabled_at. |
- cURL
- Node.js
- Python
Errors
| Status | When |
|---|---|
400 | New URL failed ping verification, unknown event type, empty events array |
401 | Auth failure |
404 | Endpoint not found |
429 | Rate limit hit |
Delete a webhook endpoint
- cURL
- Node.js
- Python
Response
List deliveries for a webhook endpoint
Query parameters
| Param | Type | Default | Notes |
|---|---|---|---|
limit | integer (1–100) | 20 | Page size. |
- cURL
- Node.js
- Python
The WebhookDelivery object
| Field | Type | Notes |
|---|---|---|
id | string (UUID) | Delivery identifier. Echoed in the X-OptimalDial-Delivery-Id header on the actual request. |
webhook_endpoint_id | string (UUID) | The endpoint we tried to deliver to. |
event_id | string (UUID) | The event being delivered. Multiple endpoints subscribed to the same event share one event_id. |
event_type | string | E.g. upload.completed. |
status | "pending" | "in_flight" | "delivered" | "exhausted" | Current state. |
attempt_count | integer | How many delivery attempts we’ve made (1-indexed). |
next_retry_at | string (ISO 8601) | When the next attempt is scheduled, if any. |
last_attempt_at | string (ISO 8601) | null | Most recent attempt time. |
last_response_status | integer | null | Receiver’s HTTP status on the most recent attempt. |
last_error | string | null | Truncated error message (e.g. timeout, DNS failure). |
created_at | string (ISO 8601) | When we enqueued this delivery. |
delivered_at | string (ISO 8601) | null | Set when status flips to delivered. |
Auto-disable
If an endpoint racks up 20 consecutive failed deliveries, we setis_active: false and stamp disabled_at. No further events go out until you re-enable it.
To bring it back online: fix whatever was wrong (likely an outage or wrong URL), then PATCH the endpoint with {"is_active": true}. That call resets consecutive_failures to 0 and clears disabled_at. Future deliveries resume immediately, but anything queued during the outage that exhausted its retries is gone — re-fetch the upload state via GET /api/v1/uploads to catch up.