Send Webhooks API Reference
This document provides a complete reference for the Hookbase webhook subscription API endpoints.
Authentication
All API endpoints require authentication using a Bearer token or API key:
# Using Bearer token
curl -H "Authorization: Bearer {token}" ...
# Using API key
curl -H "x-api-key: whr_your_api_key" ...Base URL
https://api.hookbase.app/api/organizations/{orgId}Replace {orgId} with your organization ID.
Send Event
Send an event to all subscribed endpoints.
Endpoint
POST /send-eventRequest Body
| Field | Type | Required | Description |
|---|---|---|---|
eventType | string | Yes | Event type name (e.g., order.created) |
payload | any | Yes | Event payload (JSON-serializable) |
applicationId | string | No | Target a specific application |
endpointId | string | No | Target a specific endpoint |
idempotencyKey | string | No | Unique key to prevent duplicate processing |
labels | object | No | Key-value labels for filtering |
metadata | object | No | Additional metadata stored with the event |
Request Headers
| Header | Required | Description |
|---|---|---|
Idempotency-Key | No | Same as idempotencyKey; the body field wins when both are set |
traceparent | No | W3C trace context. Deliveries continue your trace, and each endpoint receives a child traceparent. See Tracing |
tracestate | No | W3C vendor trace state, passed to endpoints unchanged |
Example Request
curl -X POST "https://api.hookbase.app/api/organizations/{orgId}/send-event" \
-H "Authorization: Bearer {token}" \
-H "Content-Type: application/json" \
-d '{
"eventType": "order.created",
"payload": {
"orderId": "ord_123",
"customerId": "cus_456",
"amount": 99.99
},
"idempotencyKey": "order-created-ord_123",
"labels": {
"region": "us-west",
"priority": "high"
}
}'Response (201 Created)
{
"data": {
"eventId": "c15e0458-dfdb-4aa7-a9b7-e03ede1e15b3",
"messagesQueued": 3,
"endpoints": [
{"id": "d80e9f49-2dbd-4203-bd12-502cfcd56a45", "url": "https://api.customer1.com/webhooks"},
{"id": "d668c499-ca2b-405d-ae69-c07294ff3127", "url": "https://api.customer2.com/webhooks"},
{"id": "4869cdc0-528c-4dad-9b95-ced3ec2aeea4", "url": "https://api.customer3.com/webhooks"}
]
}
}Error Responses
| Status | Error | Description |
|---|---|---|
| 400 | Invalid input | Request body validation failed |
| 404 | Event type not found | No enabled event type with this name |
| 413 | Payload too large | Payload exceeds plan limit |
| 415 | Invalid Content-Type | Content-Type must be application/json |
| 422 | Schema validation failed | The event type has validatePayloads on and the payload doesn't match its schema. Nothing is queued. See Validating payloads |
| 429 | Rate limit exceeded | Too many events sent |
| 429 | Monthly limit exceeded | Monthly message quota exceeded |
Event Types
List Event Types
GET /event-typesQuery Parameters
| Parameter | Type | Default | Description |
|---|---|---|---|
category | string | - | Filter by category |
isEnabled | boolean | - | Filter by enabled status |
search | string | - | Search by name |
limit | integer | 50 | Max results (1-100) |
cursor | string | - | Pagination cursor |
Example Request
curl "https://api.hookbase.app/api/organizations/{orgId}/event-types?category=orders&limit=20" \
-H "Authorization: Bearer {token}"Response (200 OK)
{
"data": [
{
"id": "2f41bb16-991e-4412-80fb-54d5a59583aa",
"name": "order.created",
"displayName": "Order Created",
"description": "Triggered when a new order is placed",
"category": "orders",
"schema": "{...}",
"examplePayload": "{...}",
"isEnabled": true,
"isDeprecated": false,
"subscriptionCount": 15,
"createdAt": "2024-01-10T10:00:00Z"
}
],
"pagination": {
"hasMore": true,
"nextCursor": "48f28bc8-9806-487d-8481-6bf47987a17f"
}
}Get Event Type
GET /event-types/{eventTypeId}Response (200 OK)
{
"data": {
"id": "2f41bb16-991e-4412-80fb-54d5a59583aa",
"name": "order.created",
"displayName": "Order Created",
"description": "Triggered when a new order is placed",
"category": "orders",
"schema": "{\"type\": \"object\", ...}",
"schemaVersion": 2,
"examplePayload": "{\"orderId\": \"ord_123\", ...}",
"documentationUrl": "https://docs.example.com/events/order-created",
"isEnabled": true,
"isDeprecated": false,
"validatePayloads": false,
"deprecatedAt": null,
"deprecatedMessage": null,
"createdAt": "2024-01-10T10:00:00Z",
"updatedAt": "2024-01-15T14:30:00Z"
}
}Create Event Type
POST /event-typesRequest Body
| Field | Type | Required | Description |
|---|---|---|---|
name | string | Yes | Unique name (lowercase, dot-separated) |
displayName | string | No | Human-readable name |
description | string | No | Description (max 1000 chars) |
category | string | No | Category for grouping |
schema | string | No | JSON Schema for validation |
examplePayload | string | No | Example payload (JSON string) |
documentationUrl | string | No | Link to documentation |
isEnabled | boolean | No | Whether active (default: true) |
validatePayloads | boolean | No | Reject sends whose payload doesn't match schema with 422 (default: false). See Validating payloads |
Example Request
curl -X POST "https://api.hookbase.app/api/organizations/{orgId}/event-types" \
-H "Authorization: Bearer {token}" \
-H "Content-Type: application/json" \
-d '{
"name": "payment.succeeded",
"displayName": "Payment Succeeded",
"description": "Triggered when a payment is successfully processed",
"category": "payments",
"schema": "{\"type\": \"object\", \"required\": [\"paymentId\", \"amount\"]}",
"examplePayload": "{\"paymentId\": \"pay_123\", \"amount\": 100.00}"
}'Response (201 Created)
{
"data": {
"id": "2f41bb16-991e-4412-80fb-54d5a59583aa",
"name": "payment.succeeded",
"displayName": "Payment Succeeded",
"isEnabled": true,
"createdAt": "2024-01-20T10:00:00Z"
}
}Update Event Type
PATCH /event-types/{eventTypeId}Request Body
| Field | Type | Description |
|---|---|---|
displayName | string | Human-readable name |
description | string | Description |
category | string | Category |
schema | string | JSON Schema (increments schemaVersion) |
examplePayload | string | Example payload |
documentationUrl | string | Documentation URL |
isEnabled | boolean | Enable/disable |
isDeprecated | boolean | Mark as deprecated |
deprecatedMessage | string | Deprecation notice |
validatePayloads | boolean | Turn schema enforcement on send on or off |
Example Request
curl -X PATCH "https://api.hookbase.app/api/organizations/{orgId}/event-types/{eventTypeId}" \
-H "Authorization: Bearer {token}" \
-H "Content-Type: application/json" \
-d '{
"isDeprecated": true,
"deprecatedMessage": "Use payment.completed instead. Removal date: 2024-06-01"
}'Delete Event Type
DELETE /event-types/{eventTypeId}Note: This cascades to delete all subscriptions for this event type.
Response (200 OK)
{
"success": true
}Export Event Catalog
GET /event-types/exportDownloads the catalog as an AsyncAPI 3.0 document, an OpenAPI 3.1 webhooks document, or a TypeScript .d.ts. See Event Catalog Export.
Query Parameters
| Parameter | Type | Description |
|---|---|---|
format | string | asyncapi (default), openapi or typescript |
encoding | string | yaml (default) or json. Ignored for typescript |
includeDisabled | boolean | Include disabled event types (default false) |
includeDeprecated | boolean | Include deprecated event types (default true) |
The public catalog serves the same export at GET /api/event-catalog/{orgSlug}/_export, without authentication. It includes only published, enabled event types.
Webhook Applications
List Applications
GET /webhook-applicationsQuery Parameters
| Parameter | Type | Default | Description |
|---|---|---|---|
search | string | - | Search by name |
isDisabled | boolean | - | Filter by disabled status |
limit | integer | 50 | Max results (1-100) |
cursor | string | - | Pagination cursor |
orderBy | string | created_at | Sort field |
order | string | desc | Sort direction (asc/desc) |
Response (200 OK)
{
"data": [
{
"id": "085fc909-649d-4820-9c73-6bf2dc484c48",
"externalId": "customer_123",
"name": "Acme Corp",
"metadata": {"plan": "enterprise"},
"rateLimitPerSecond": 100,
"rateLimitPerMinute": 1000,
"rateLimitPerHour": 10000,
"isDisabled": false,
"totalEndpoints": 3,
"totalMessagesSent": 15420,
"totalMessagesFailed": 23,
"lastEventAt": "2024-01-20T15:30:00Z",
"createdAt": "2024-01-01T10:00:00Z"
}
],
"pagination": {
"hasMore": false,
"nextCursor": null
}
}Get Application
GET /webhook-applications/{applicationId}Get Application by External ID
GET /webhook-applications/by-external-id/{externalId}Useful for looking up applications using your internal customer ID.
Create Application
POST /webhook-applicationsRequest Body
| Field | Type | Required | Description |
|---|---|---|---|
name | string | Yes | Application name |
externalId | string | No | Your internal customer ID |
metadata | object | No | Custom metadata |
rateLimitPerSecond | integer | No | Rate limit (default: 100) |
rateLimitPerMinute | integer | No | Rate limit (default: 1000) |
rateLimitPerHour | integer | No | Rate limit (default: 10000) |
Example Request
curl -X POST "https://api.hookbase.app/api/organizations/{orgId}/webhook-applications" \
-H "Authorization: Bearer {token}" \
-H "Content-Type: application/json" \
-d '{
"name": "Acme Corp",
"externalId": "customer_123",
"metadata": {
"plan": "enterprise",
"accountManager": "jane@example.com"
}
}'Upsert Application
PUT /webhook-applications/upsertCreate or update an application by external ID. Useful for syncing from your customer database.
Request Body
| Field | Type | Required | Description |
|---|---|---|---|
externalId | string | Yes | Your internal customer ID |
name | string | Yes | Application name |
metadata | object | No | Custom metadata |
rateLimitPerSecond | integer | No | Rate limit |
rateLimitPerMinute | integer | No | Rate limit |
rateLimitPerHour | integer | No | Rate limit |
Response (200 OK or 201 Created)
{
"data": {...},
"created": true
}Update Application
PATCH /webhook-applications/{applicationId}Request Body
| Field | Type | Description |
|---|---|---|
name | string | Application name |
metadata | object | Custom metadata |
rateLimitPerSecond | integer | Rate limit |
rateLimitPerMinute | integer | Rate limit |
rateLimitPerHour | integer | Rate limit |
isDisabled | boolean | Disable/enable |
disabledReason | string | Reason for disabling |
Delete Application
DELETE /webhook-applications/{applicationId}Note: This cascades to delete all endpoints and subscriptions.
Application Usage
GET /webhook-applications/usage?from=2026-09-01&to=2026-09-30&granularity=monthPer-application usage (events sent, messages created, delivery attempts, successful deliveries,
dead-lettered messages, bytes delivered) by UTC day or month, as JSON or a streamed CSV
(format=csv). See Usage Export for the parameters and how each metric
is counted.
Webhook Endpoints
List Endpoints
GET /webhook-endpointsQuery Parameters
| Parameter | Type | Default | Description |
|---|---|---|---|
applicationId | string | - | Filter by application |
isDisabled | boolean | - | Filter by disabled status |
circuitState | string | - | Filter by circuit state (closed/open/half_open) |
limit | integer | 50 | Max results (1-100) |
cursor | string | - | Pagination cursor |
Response (200 OK)
{
"data": [
{
"id": "d80e9f49-2dbd-4203-bd12-502cfcd56a45",
"applicationId": "dedb8cf3-6765-4ddb-88c0-76bc31df3f3c",
"url": "https://api.customer.com/webhooks",
"description": "Production endpoint",
"secretPrefix": "whsec_...",
"hasSecret": true,
"signatureScheme": "hmac-sha256",
"publicKey": null,
"timeoutSeconds": 30,
"isDisabled": false,
"isVerified": true,
"circuitState": "closed",
"circuitFailureCount": 0,
"totalMessages": 1542,
"totalSuccesses": 1520,
"totalFailures": 22,
"avgResponseTimeMs": 145,
"lastSuccessAt": "2024-01-20T15:30:00Z",
"subscriptionCount": 5,
"createdAt": "2024-01-01T10:00:00Z"
}
],
"pagination": {
"hasMore": false,
"nextCursor": null
}
}Get Endpoint
GET /webhook-endpoints/{endpointId}Create Endpoint
POST /webhook-endpointsRequest Body
| Field | Type | Required | Description |
|---|---|---|---|
applicationId | string | Yes | Parent application ID |
url | string | Yes | HTTPS endpoint URL |
description | string | No | Description (max 500 chars) |
headers | array | No | Custom headers to include |
timeoutSeconds | integer | No | Request timeout (default: 30, max: 120) |
rateLimitPerSecond | integer | No | Endpoint rate limit (0 = unlimited) |
circuitFailureThreshold | integer | No | Failures to open circuit (default: 5) |
circuitSuccessThreshold | integer | No | Successes to close circuit (default: 2) |
circuitCooldownSeconds | integer | No | Cooldown period (default: 60) |
signatureScheme | string | No | hmac-sha256 (default, v1 signatures with a whsec_ secret) or ed25519 (v1a signatures; Hookbase generates the key pair and returns only the whpk_ public key). See Ed25519 signatures |
authentication | object | No | HTTP Basic or OAuth2 client credentials sent as the Authorization header (Starter plan and above). See Endpoint Authentication |
Headers Format
{
"headers": [
{"name": "X-Custom-Header", "value": "my-value"},
{"name": "Authorization", "value": "Bearer token123"}
]
}Example Request
curl -X POST "https://api.hookbase.app/api/organizations/{orgId}/webhook-endpoints" \
-H "Authorization: Bearer {token}" \
-H "Content-Type: application/json" \
-d '{
"applicationId": "085fc909-649d-4820-9c73-6bf2dc484c48",
"url": "https://api.customer.com/webhooks",
"description": "Production webhook endpoint",
"timeoutSeconds": 30,
"headers": [
{"name": "X-API-Version", "value": "2024-01-01"}
],
"circuitFailureThreshold": 10
}'Response (201 Created)
{
"data": {
"id": "d80e9f49-2dbd-4203-bd12-502cfcd56a45",
"url": "https://api.customer.com/webhooks",
"secret": "whsec_abc123def456ghi789...",
"circuitState": "closed"
},
"warning": "Save the signing secret now. It will not be shown again."
}With "signatureScheme": "ed25519" the response has no secret; it carries "signatureScheme": "ed25519" and "publicKey": "whpk_..." instead, and publicKey is returned by every later GET too. The private key is never returned.
Update Endpoint
PATCH /webhook-endpoints/{endpointId}Request Body
| Field | Type | Description |
|---|---|---|
url | string | Endpoint URL |
description | string | Description |
headers | array | Custom headers |
timeoutSeconds | integer | Request timeout |
isDisabled | boolean | Disable/enable |
disabledReason | string | Reason for disabling |
rateLimitPerSecond | integer | Rate limit |
circuitFailureThreshold | integer | Circuit breaker config |
circuitSuccessThreshold | integer | Circuit breaker config |
circuitCooldownSeconds | integer | Circuit breaker config |
authentication | object | null | Replace the endpoint's credentials; with the same type, leaving out password / clientSecret keeps the stored one. null removes them. See Endpoint Authentication |
signatureScheme | string | hmac-sha256 or ed25519. A different scheme generates a new key of that kind right away, bumps secretVersion and ends any rotation grace period. Switching to hmac-sha256 returns the new secret once as data.secret. Sending the current scheme changes nothing |
Rotate Endpoint Secret
POST /webhook-endpoints/{endpointId}/rotate-secretRotates the signing secret with optional grace period during which both old and new secrets are valid.
Request Body
| Field | Type | Default | Description |
|---|---|---|---|
gracePeriodSeconds | integer | 3600 | Grace period (max: 86400) |
Response (200 OK)
{
"signatureScheme": "hmac-sha256",
"secret": "whsec_newkey123...",
"previousSecretExpiresAt": "2024-01-20T16:00:00Z",
"secretVersion": 2
}For an ed25519 endpoint a new key pair is generated and the response has the public keys instead of a secret. Until previousSecretExpiresAt deliveries are signed with both keys (v1a,<old> v1a,<new>):
{
"signatureScheme": "ed25519",
"publicKey": "whpk_new...",
"previousPublicKey": "whpk_old...",
"previousSecretExpiresAt": "2024-01-20T16:00:00Z",
"secretVersion": 2
}Reset Circuit Breaker
POST /webhook-endpoints/{endpointId}/reset-circuitManually reset a circuit breaker to the closed state.
Response (200 OK)
{
"success": true,
"circuitState": "closed"
}Delete Endpoint
DELETE /webhook-endpoints/{endpointId}Note: This cascades to delete all subscriptions for this endpoint.
Webhook Subscriptions
List Subscriptions
GET /webhook-subscriptionsQuery Parameters
| Parameter | Type | Default | Description |
|---|---|---|---|
endpointId | string | - | Filter by endpoint |
eventTypeId | string | - | Filter by event type |
applicationId | string | - | Filter by application |
isEnabled | boolean | - | Filter by enabled status |
limit | integer | 50 | Max results (1-100) |
cursor | string | - | Pagination cursor |
Response (200 OK)
{
"data": [
{
"id": "ce9fa4a9-8f21-41cf-9e2c-2fb8cc8556d5",
"endpointId": "d668c499-ca2b-405d-ae69-c07294ff3127",
"eventTypeId": "33d15aa3-577a-42fa-8119-c7921c3b8dfa",
"filterExpression": null,
"labelFilters": {"region": "us-west"},
"labelFilterMode": "all",
"transformId": null,
"isEnabled": true,
"endpointUrl": "https://api.customer.com/webhooks",
"eventTypeName": "order.created",
"eventTypeDisplayName": "Order Created",
"applicationId": "e5e49a60-bd0c-4432-a068-fa0414d2c7b4",
"applicationName": "Acme Corp",
"createdAt": "2024-01-10T10:00:00Z"
}
],
"pagination": {
"hasMore": false,
"nextCursor": null
}
}Get Subscription
GET /webhook-subscriptions/{subscriptionId}Create Subscription
POST /webhook-subscriptionsRequest Body
| Field | Type | Required | Description |
|---|---|---|---|
endpointId | string | Yes | Target endpoint |
eventTypeId | string | Yes | Event type to subscribe to |
filterExpression | string | No | JSONata expression evaluated against the event payload (max 1000 chars). See Filter Expressions. Blank means no filter. |
labelFilters | object | No | Label filter conditions |
labelFilterMode | string | No | Filter mode: all or any |
transformId | string | No | ID of one of your organization's transforms, applied before signing. See Subscription Transforms. |
isEnabled | boolean | No | Whether active (default: true) |
Label Filters Format
{
"labelFilters": {
"region": "us-west",
"priority": ["high", "urgent"],
"environment": "production"
},
"labelFilterMode": "all"
}Example Request
curl -X POST "https://api.hookbase.app/api/organizations/{orgId}/webhook-subscriptions" \
-H "Authorization: Bearer {token}" \
-H "Content-Type: application/json" \
-d '{
"endpointId": "d80e9f49-2dbd-4203-bd12-502cfcd56a45",
"eventTypeId": "48f28bc8-9806-487d-8481-6bf47987a17f",
"labelFilters": {
"region": "us-west"
},
"labelFilterMode": "all"
}'Error Response (409 Conflict)
{
"error": "Duplicate subscription",
"message": "This endpoint is already subscribed to this event type"
}Error Response (400 Bad Request)
A filterExpression that isn't valid JSONata is rejected when the subscription is saved:
{
"error": "Validation failed",
"details": {
"formErrors": [],
"fieldErrors": {
"filterExpression": ["Invalid JSONata filter expression: ..."]
}
}
}A transformId that doesn't belong to your organization returns 404 with {"error": "Transform not found"}.
Filter Expressions
A subscription's filterExpression is a JSONata expression. When you send an event, it is evaluated against the event's payload for each subscription that matches the event type (and its label filters, which are checked first). The subscription receives the event only when the result is truthy by JSONata's $boolean rules:
| Result | Delivered? |
|---|---|
true, a non-zero number, a non-empty string, an object with at least one key, an array containing a truthy value | Yes |
false, 0, "", null, {}, [] (or an array of only falsy values), a path that matches nothing | No |
Two variables are bound alongside the payload:
| Variable | Value |
|---|---|
$eventType | The event type name, e.g. "order.created" |
$labels | The labels sent with the event (an empty object when none were sent) |
Examples:
order.total > 100
customer.tier in ["pro", "enterprise"]
$labels.region = "eu" and status != "test"- Expressions are checked when the subscription is created or updated; one that doesn't compile is rejected with
400. - An expression that throws at send time (for example on an unexpected payload shape) or runs longer than 1 second counts as no match: the subscription is skipped for that event rather than delivered to.
- A blank or
nullfilterExpressionmeans no filter. Set it tonullwith Update Subscription to remove a filter.
Subscription Transforms
Set transformId to one of your organization's transforms to reshape the payload for one endpoint. The delivery worker applies it to the event payload before signing, so the signature headers cover the body the endpoint actually receives, and the request's Content-Type follows the transform's output format.
- The transform is read at delivery time, so an edited transform also applies to retries and replays.
- An inactive transform is skipped: the endpoint receives the untransformed payload.
- A transform that throws fails the same way on every attempt, so the message is not retried; it moves straight to the dead letter queue with reason
transform_failedand the transform's error message. - Set
transformIdtonullwith Update Subscription to detach it.
Bulk Create Subscriptions
POST /webhook-subscriptions/bulkSubscribe an endpoint to multiple event types at once.
Request Body
| Field | Type | Required | Description |
|---|---|---|---|
endpointId | string | Yes | Target endpoint |
eventTypeIds | array | Yes | Event type IDs (max 100) |
labelFilters | object | No | Label filters for all subscriptions |
labelFilterMode | string | No | Filter mode: all or any |
filterExpression | string | No | Filter expression applied to every created subscription |
transformId | string | No | Transform applied to every created subscription |
Event types the endpoint is already subscribed to are skipped (and counted in skipped); their existing settings are not changed.
Example Request
curl -X POST "https://api.hookbase.app/api/organizations/{orgId}/webhook-subscriptions/bulk" \
-H "Authorization: Bearer {token}" \
-H "Content-Type: application/json" \
-d '{
"endpointId": "d80e9f49-2dbd-4203-bd12-502cfcd56a45",
"eventTypeIds": [
"b862fc8c-2298-4065-aba4-51745d13ab8c",
"e847fdac-a158-4913-b277-d10ee037ad25",
"adef9d21-744b-45bc-b3f5-96c4fd82c840"
]
}'Response (201 Created)
{
"created": 3,
"skipped": 0,
"subscriptions": [...]
}Update Subscription
PATCH /webhook-subscriptions/{subscriptionId}Request Body
Only the fields you send are changed. null clears a field.
| Field | Type | Description |
|---|---|---|
filterExpression | string | null | Filter expression; null removes the filter |
labelFilters | object | null | Label filter conditions; null removes them |
labelFilterMode | string | Filter mode: all or any |
transformId | string | null | Transform ID; null detaches it |
isEnabled | boolean | Enable/disable |
Delete Subscription
DELETE /webhook-subscriptions/{subscriptionId}Outbound Messages
List Messages
GET /outbound-messagesQuery Parameters
| Parameter | Type | Default | Description |
|---|---|---|---|
status | string | - | Filter by status |
eventType | string | - | Filter by event type name |
applicationId | string | - | Filter by application |
endpointId | string | - | Filter by endpoint |
limit | integer | 50 | Max results (1-100) |
cursor | string | - | Pagination cursor |
Status Values
| Status | Description |
|---|---|
pending | Queued for delivery |
processing | Currently being delivered |
success | Successfully delivered |
failed | Delivery failed (will retry) |
exhausted | All retries exhausted |
dlq | Moved to dead letter queue |
Response (200 OK)
{
"data": [
{
"id": "9aed2721-d4cb-4a39-ad01-a09dafb99533",
"eventId": "4c149982-5dc4-47c4-9638-65cfa07934f5",
"eventType": "order.created",
"applicationId": "62c50197-3522-481f-b285-dbd946a2cb02",
"endpointId": "bb1daf5c-2251-4883-ad56-37182fdaf7b8",
"status": "success",
"attempts": 1,
"maxAttempts": 5,
"lastResponseStatus": 200,
"lastErrorMessage": null,
"createdAt": "2024-01-20T10:00:00Z",
"completedAt": "2024-01-20T10:00:01Z",
"endpointUrl": "https://api.customer.com/webhooks",
"applicationName": "Acme Corp"
}
],
"pagination": {
"hasMore": true,
"nextCursor": "2024-01-20T09:59:00Z"
}
}Get Message Details
GET /outbound-messages/{messageId}The response includes traceId: the W3C trace ID that the message's delivery attempts belong to. See Tracing.
Get Message Attempts
GET /outbound-messages/{messageId}/attemptsResponse (200 OK)
{
"data": [
{
"id": "ec15ed1b-5a54-42ff-9da9-7db6c53edb05",
"messageId": "be9df68e-d79b-4ada-b3a2-71922c515a58",
"attemptNumber": 1,
"status": "success",
"responseStatus": 200,
"responseTimeMs": 145,
"responseBody": "{\"received\": true}",
"errorType": null,
"errorMessage": null,
"createdAt": "2024-01-20T10:00:00Z",
"completedAt": "2024-01-20T10:00:00Z"
}
]
}Replay Message
POST /outbound-messages/{messageId}/replayCreate a new delivery attempt for a failed or exhausted message.
Response (201 Created)
{
"data": {
"originalMessageId": "9aed2721-d4cb-4a39-ad01-a09dafb99533",
"newMessageId": "be9df68e-d79b-4ada-b3a2-71922c515a58",
"status": "queued"
}
}Get Message Statistics
GET /outbound-messages/stats/summaryResponse (200 OK)
{
"data": {
"pending": 12,
"processing": 3,
"success": 15420,
"failed": 45,
"exhausted": 8,
"dlq": 5,
"total": 15493
}
}Export Messages
GET /outbound-messages/exportQuery Parameters
| Parameter | Type | Default | Description |
|---|---|---|---|
format | string | json | Export format (json/csv) |
type | string | events | Export type (events/messages) |
startDate | string | - | Start date (ISO 8601) |
endDate | string | - | End date (ISO 8601) |
status | string | - | Filter by status |
eventType | string | - | Filter by event type |
applicationId | string | - | Filter by application |
limit | integer | 10000 | Max results (max: 50000) |
Dead Letter Queue (DLQ)
List DLQ Messages
GET /outbound-messages/dlq/messagesQuery Parameters
| Parameter | Type | Default | Description |
|---|---|---|---|
reason | string | - | Filter by DLQ reason |
eventType | string | - | Filter by event type |
applicationId | string | - | Filter by application |
endpointId | string | - | Filter by endpoint |
startDate | string | - | Filter by dlqMovedAt start |
endDate | string | - | Filter by dlqMovedAt end |
limit | integer | 50 | Max results (1-100) |
cursor | string | - | Pagination cursor |
DLQ Reasons
| Reason | Description |
|---|---|
max_retries_exceeded | All retry attempts failed |
circuit_breaker_exhausted | Circuit breaker open, retries exhausted |
endpoint_not_found | Endpoint was deleted |
payload_not_found | Payload missing from storage |
secret_decryption_failed | Could not decrypt signing secret |
transform_failed | The subscription's transform threw; not retried |
endpoint_disabled | Not sent because the endpoint is disabled; can be retried once the endpoint is re-enabled |
endpoint_gone | The receiver answered 410 Gone; the endpoint was auto-disabled |
Response (200 OK)
{
"data": [
{
"id": "9aed2721-d4cb-4a39-ad01-a09dafb99533",
"eventId": "4c149982-5dc4-47c4-9638-65cfa07934f5",
"eventType": "order.created",
"status": "dlq",
"attempts": 5,
"maxAttempts": 5,
"lastResponseStatus": 500,
"lastErrorType": "http_error",
"lastErrorMessage": "HTTP 500: Internal Server Error",
"dlqReason": "max_retries_exceeded",
"dlqMovedAt": "2024-01-20T10:30:00Z",
"dlqRetryCount": 0,
"dlqLastRetryAt": null,
"endpointUrl": "https://api.customer.com/webhooks",
"applicationName": "Acme Corp"
}
],
"pagination": {
"hasMore": false,
"nextCursor": null
}
}Get DLQ Statistics
GET /outbound-messages/dlq/statsResponse (200 OK)
{
"data": {
"total": 25,
"byReason": {
"max_retries_exceeded": 20,
"circuit_breaker_exhausted": 3,
"endpoint_not_found": 2
},
"topFailingEndpoints": [
{
"endpointId": "d80e9f49-2dbd-4203-bd12-502cfcd56a45",
"endpointUrl": "https://api.failing.com/webhooks",
"count": 15
}
]
}
}Retry DLQ Message
POST /outbound-messages/dlq/{messageId}/retryResponse (201 Created)
{
"data": {
"originalMessageId": "9aed2721-d4cb-4a39-ad01-a09dafb99533",
"newMessageId": "be9df68e-d79b-4ada-b3a2-71922c515a58",
"status": "queued"
}
}Bulk Retry DLQ Messages
POST /outbound-messages/dlq/retry-bulkRequest Body
{
"messageIds": ["9aed2721-d4cb-4a39-ad01-a09dafb99533", "be9df68e-d79b-4ada-b3a2-71922c515a58", "521c0985-6cfb-4225-98d4-55b2fc9db3c8"]
}Note: Maximum 100 messages per request.
Response (201 Created)
{
"data": {
"queued": 3,
"skipped": 0,
"messages": [
{"originalMessageId": "9aed2721-d4cb-4a39-ad01-a09dafb99533", "newMessageId": "fde42c35-aeec-4966-be09-f2f10514da2b"},
{"originalMessageId": "be9df68e-d79b-4ada-b3a2-71922c515a58", "newMessageId": "562eb4fc-ab88-4565-bf9c-bb8ebb6ff0d1"},
{"originalMessageId": "521c0985-6cfb-4225-98d4-55b2fc9db3c8", "newMessageId": "633cd77a-f7d4-46a9-aa8a-96d9963e8cdd"}
]
}
}Delete DLQ Message
DELETE /outbound-messages/dlq/{messageId}Archives the message (marks as exhausted).
Bulk Delete DLQ Messages
DELETE /outbound-messages/dlq/bulkRequest Body
{
"messageIds": ["9aed2721-d4cb-4a39-ad01-a09dafb99533", "be9df68e-d79b-4ada-b3a2-71922c515a58"]
}Analytics
Get Delivery Analytics
GET /webhooks/analyticsQuery Parameters
| Parameter | Type | Default | Description |
|---|---|---|---|
timeRange | string | 24h | Time range (1h/24h/7d/30d) |
applicationId | string | - | Filter by application |
endpointId | string | - | Filter by endpoint |
Response (200 OK)
{
"data": {
"timeRange": "24h",
"startTime": "2024-01-19T10:00:00Z",
"summary": {
"totalDeliveries": 15420,
"successCount": 15350,
"failedCount": 70,
"pendingCount": 12,
"processingCount": 3,
"dlqCount": 5,
"successRate": 99.55
},
"latency": {
"p50": 120,
"p95": 450,
"p99": 890,
"avg": 145
},
"topFailingEndpoints": [
{
"endpointId": "d80e9f49-2dbd-4203-bd12-502cfcd56a45",
"endpointUrl": "https://api.slow.com/webhooks",
"circuitState": "half_open",
"failureCount": 25,
"totalMessages": 500,
"totalSuccesses": 475,
"totalFailures": 25
}
],
"errorTypes": [
{"type": "timeout", "count": 30},
{"type": "http_error", "count": 25},
{"type": "network", "count": 15}
],
"dlqByReason": {
"max_retries_exceeded": 4,
"circuit_breaker_exhausted": 1
},
"chartData": [
{"period": "2024-01-19 10:00", "success": 450, "failed": 3, "pending": 0},
{"period": "2024-01-19 11:00", "success": 520, "failed": 2, "pending": 0}
]
}
}Get Endpoint Analytics
GET /webhooks/analytics/endpoints/{endpointId}Query Parameters
| Parameter | Type | Default | Description |
|---|---|---|---|
timeRange | string | 24h | Time range (1h/24h/7d/30d) |
Response (200 OK)
{
"data": {
"endpoint": {
"id": "d80e9f49-2dbd-4203-bd12-502cfcd56a45",
"url": "https://api.customer.com/webhooks",
"circuitState": "closed",
"circuitFailureCount": 0,
"totalMessages": 1542,
"totalSuccesses": 1520,
"totalFailures": 22,
"avgResponseTimeMs": 145,
"lastSuccessAt": "2024-01-20T15:30:00Z",
"lastFailureAt": "2024-01-18T10:00:00Z"
},
"stats": {
"success": 320,
"failed": 5,
"pending": 2,
"avgResponseTimeMs": 142
},
"recentAttempts": [
{
"id": "ec15ed1b-5a54-42ff-9da9-7db6c53edb05",
"messageId": "be9df68e-d79b-4ada-b3a2-71922c515a58",
"attemptNumber": 1,
"status": "success",
"responseStatus": 200,
"responseTimeMs": 125,
"createdAt": "2024-01-20T15:30:00Z"
}
]
}
}Error Responses
All error responses follow this format:
{
"error": "Error type",
"message": "Detailed error message",
"details": {
"field": ["Validation error message"]
}
}Common Error Codes
| Status | Error | Description |
|---|---|---|
| 400 | Invalid input | Request validation failed |
| 401 | Unauthorized | Missing or invalid authentication |
| 403 | Forbidden | Insufficient permissions or feature not enabled |
| 404 | Not found | Resource does not exist |
| 409 | Conflict | Duplicate resource |
| 413 | Payload too large | Request body exceeds limit |
| 429 | Rate limited | Too many requests |
| 500 | Internal error | Server error |
Rate Limits
| Endpoint | Limit |
|---|---|
POST /send-event | Plan-based (100-10000 events/min) |
| Other endpoints | 1000 requests/minute |
Rate limit headers are included in responses:
X-RateLimit-Limit: 1000
X-RateLimit-Remaining: 995
X-RateLimit-Reset: 1705750800