Developer Webhooks API
Developer Webhooks API
All endpoints are app-scoped under:
/api/v1/apps/:appId/webhooks
Requires authentication via Authorization: Bearer <accessToken>.
List Subscriptions
GET /api/v1/apps/:appId/webhooks/subscriptions?page=1&limit=20&status=active| Parameter | Type | Default | Description |
|---|---|---|---|
page | integer | 1 | Page number |
limit | integer | 20 | Results per page (max 100) |
status | string | -- | Filter: active or inactive |
Response (200):
{
"status": 200,
"message": "Webhook subscriptions fetched",
"data": {
"items": [
{
"subscription_id": 5,
"installation_id": 2,
"app_id": 2,
"store_id": 22,
"name": "Order Status Webhook",
"event_topic": "order.status_changed",
"target_url": "https://api.example.com/webhooks",
"permissions": ["read_orders"],
"is_active": 1,
"failure_count": 0,
"last_failure_at": null,
"last_success_at": "2026-02-28T10:15:00.000Z",
"created_at": "2026-01-20T09:00:00.000Z",
"updated_at": "2026-02-28T10:15:00.000Z"
}
],
"page": 1,
"limit": 20,
"total": 1,
"totalPages": 1
}
}Create Subscription
POST /api/v1/apps/:appId/webhooks/subscriptions
Content-Type: application/jsonRequest Body:
{
"name": "Orders Webhook",
"event_topic": "order.created",
"target_url": "https://example.com/webhooks/orders",
"permissions": ["read_orders"]
}| Field | Type | Required | Description |
|---|---|---|---|
name | string | No | Display name (2-255 chars) |
event_topic | string | Yes | Event to subscribe to (3-100 chars) |
target_url | string | Yes | URL to deliver webhooks to (valid URL) |
permissions | string[] | No | Payload filter permissions (max 50 items) |
Response (201):
{
"status": 201,
"message": "Webhook subscription created",
"data": {
"subscription_id": 6,
"app_id": 2,
"event_topic": "order.created",
"target_url": "https://example.com/webhooks/orders",
"signing_secret": "whsec_a1b2c3d4e5f6g7h8i9j0...",
"created_at": "2026-02-28T12:00:00.000Z"
}
}Save the signing secret
The signing_secret (prefixed whsec_) is shown only once at creation time. Use it to verify HMAC signatures on incoming webhook deliveries. See Receiving Webhooks for verification code.
Errors:
400 INSTALLATION_REQUIRED— No active installation found for this app409 WEBHOOK_EXISTS— A subscription for this topic already exists
Update Subscription
PUT /api/v1/apps/:appId/webhooks/subscriptions/:subscriptionId
Content-Type: application/jsonRequest Body (all fields optional):
{
"name": "Updated Webhook Name",
"target_url": "https://example.com/webhooks/new-endpoint",
"permissions": ["read_orders", "read_customers"],
"is_active": true
}| Field | Type | Required | Description |
|---|---|---|---|
name | string | No | Updated display name (2-255 chars) |
event_topic | string | No | Updated event topic (3-100 chars) |
target_url | string | No | Updated delivery URL |
permissions | string[] | No | Updated payload filter permissions |
is_active | boolean | No | Enable/disable the subscription |
Response (200):
{
"status": 200,
"message": "Webhook subscription updated",
"data": {
"subscription_id": 6,
"app_id": 2,
"name": "Updated Webhook Name",
"event_topic": "order.created",
"target_url": "https://example.com/webhooks/new-endpoint",
"permissions": ["read_orders", "read_customers"],
"is_active": 1,
"updated_at": "2026-02-28T12:30:00.000Z"
}
}Delete Subscription
DELETE /api/v1/apps/:appId/webhooks/subscriptions/:subscriptionIdSoft-deletes the subscription (is_active = 0, deleted_at set).
Response (200):
{
"status": 200,
"message": "Webhook subscription deleted",
"data": {
"subscription_id": 6,
"deleted": true
}
}List Deliveries
View webhook delivery attempts with filtering:
GET /api/v1/apps/:appId/webhooks/deliveries?page=1&limit=20&status=failed&event_topic=order.created| Parameter | Type | Default | Description |
|---|---|---|---|
page | integer | 1 | Page number |
limit | integer | 20 | Results per page (max 100) |
status | string | -- | Filter: pending, success, or failed |
event_topic | string | -- | Filter by event topic |
Response (200):
{
"status": 200,
"message": "Webhook deliveries fetched",
"data": {
"items": [
{
"delivery_id": "12345",
"subscription_id": 5,
"installation_id": 2,
"store_id": 22,
"event_topic": "order.status_changed",
"event_id": "550e8400-e29b-41d4-a716-446655440000",
"target_url": "https://api.example.com/webhooks",
"response_status": 200,
"response_body": "{\"received\":true}",
"attempt_number": 1,
"status": "success",
"duration_ms": 245,
"error_message": null,
"created_at": "2026-02-28T10:15:00.000Z",
"completed_at": "2026-02-28T10:15:00.245Z"
}
],
"page": 1,
"limit": 20,
"total": 1,
"totalPages": 1
}
}Retry Delivery
Retry a failed webhook delivery. Creates a new delivery attempt with incremented attempt_number.
POST /api/v1/apps/:appId/webhooks/deliveries/:deliveryId/retryResponse (200):
{
"status": 200,
"message": "Webhook retry queued",
"data": {
"delivery_id": "12346",
"status": "pending",
"attempt_number": 2,
"created_at": "2026-02-28T12:00:00.000Z"
}
}Test Webhook
Send a test webhook delivery to your app's webhook_receive_url. Useful for verifying your endpoint is reachable and correctly verifying signatures.
POST /api/v1/apps/:appId/webhooks/test
Content-Type: application/jsonRequest Body (optional):
{
"event_topic": "test.ping"
}| Field | Type | Default | Description |
|---|---|---|---|
event_topic | string | test.ping | Topic for the test event (3-100 chars) |
Response (200):
{
"status": 200,
"message": "Webhook test completed",
"data": {
"delivered": true,
"target_url": "https://api.example.com/webhooks",
"event_topic": "test.ping",
"event_id": "550e8400-e29b-41d4-a716-446655440001",
"response_status": 200,
"response_body": "{\"received\":true}",
"duration_ms": 189,
"error_message": null
}
}Errors:
400 NO_WEBHOOK_URL— App has nowebhook_receive_urlconfigured
Auto-Disable Behavior
If a webhook subscription accumulates 20 consecutive failed deliveries, the platform automatically deactivates it (is_active = 0). You can re-enable it via the Update Subscription endpoint after fixing the underlying issue.
The delivery log (failure_count, last_failure_at, last_success_at) helps you monitor subscription health and catch issues before auto-disable kicks in.