Webhooks API
Admin endpoints for managing outbound webhooks. Reading webhooks and deliveries needs settings.view or settings.integrations; creating, updating, deleting, testing and replaying need settings.integrations. All routes use the same admin authentication as the rest of the admin panel (cookie or Authorization: Bearer <jwt>).
For payload shape, signature verification, retry semantics, and the deliveries operational view, see the Webhooks feature page.
List webhooks
API_URL="http://localhost:3001/api"
TOKEN="your-jwt-token"
curl --fail --silent --show-error \
-X GET "$API_URL/admin/webhooks" \
-H "Authorization: Bearer $TOKEN" | jqReturns an array of webhook rows. The signing secret is never returned after creation — only secret_preview (first 8 chars) is exposed.
Create a webhook
curl --fail --silent --show-error \
-X POST "$API_URL/admin/webhooks" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{
"name": "n8n WhatsApp",
"url": "https://n8n.example.com/webhook/picpeak",
"events": ["event.published"],
"active": true
}' | jqThe response includes the plaintext signing secret in a secret field. Save it now — it is never recoverable after this call. Configure your receiver to compute HMAC-SHA256(secret, raw_body) and compare against the X-PicPeak-Signature header.
Required fields
| Field | Type | Description |
|---|---|---|
name | string (1–100) | Display name for the admin UI |
url | string (≤2048) | Receiver URL. Validated against the SSRF blocklist unless WEBHOOK_ALLOW_PRIVATE_URLS=true |
events | array | Subscribed event types (subset of the catalog) |
Optional fields
| Field | Type | Default | Description |
|---|---|---|---|
active | boolean | true | Inactive webhooks are skipped at fire time |
filter | object | {} | Dot-path predicate; AND of all keys; arrays = “any of” |
template | string | null | ${dot.path} substitution applied to the request body. Validated at create time. null = use the default JSON envelope |
Subscribed event types
event.created, event.published, event.archived, event.expired, photo.uploaded, photo.deleted, and on the beta channel from v3.171.0-beta.0 selection.submitted.
selection.submitted fires when a guest presses Finish selection or Approve edited photos in a gallery that allows it. Its data carries the event and a submission:
{
"submission": {
"id": 12,
"kind": "selection",
"guest_name": "Emily",
"count": 48,
"note": "Please retouch the group shot.",
"submitted_at": "2026-10-12T18:04:11.000Z"
}
}kind is selection or approval; an approval also carries rejected_count. guest_name and note may be null. See Feedback.
A photo deleted into the photo trash fires photo.deleted at that moment, not again when the trash is purged.
Update a webhook
curl --fail --silent --show-error \
-X PUT "$API_URL/admin/webhooks/$WEBHOOK_ID" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{ "active": false }' | jqSame field set as create. Send only the keys you want to change. The signing secret cannot be rotated via this endpoint — delete and recreate.
Delete a webhook
curl -X DELETE "$API_URL/admin/webhooks/$WEBHOOK_ID" \
-H "Authorization: Bearer $TOKEN"Cascades to webhook_deliveries — all queued and historical deliveries are removed.
Send a test event
Fires a synthetic delivery against the webhook with a stub payload. Useful for verifying the receiver is reachable and the signature checks pass. Bypasses subscription matching but respects the active flag.
curl --fail --silent --show-error \
-X POST "$API_URL/admin/webhooks/$WEBHOOK_ID/test" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{ "event_type": "event.published" }' | jqevent_type defaults to the first subscribed event if omitted. Returns 202 Accepted.
List deliveries
curl --fail --silent --show-error \
-X GET "$API_URL/admin/webhooks/$WEBHOOK_ID/deliveries?status=failed&page=1&limit=25" \
-H "Authorization: Bearer $TOKEN" | jq| Query param | Description |
|---|---|
status | Filter by pending, success, or failed. Omit for all. |
page | 1-based, default 1 |
limit | Default 25, max 100 |
Response: { deliveries: [...], pagination: { page, limit, total } }. Each row has id, event_type, status, attempt_count, response_status, latency_ms, next_retry_at, last_error, created_at, completed_at (no payload — see next endpoint).
Get delivery detail
curl --fail --silent --show-error \
-X GET "$API_URL/admin/webhooks/$WEBHOOK_ID/deliveries/$DELIVERY_ID" \
-H "Authorization: Bearer $TOKEN" | jqReturns the full row including the payload that was POSTed (signed body) and the response_body (truncated to 1KB).
Replay a delivery
Re-enqueues a delivery as a fresh attempt. The original row stays as audit log; the new row carries payload.replayed_from = <original_id>.
curl --fail --silent --show-error \
-X POST "$API_URL/admin/webhooks/$WEBHOOK_ID/deliveries/$DELIVERY_ID/replay" \
-H "Authorization: Bearer $TOKEN" | jqWorks for any delivery (success, failed, or pending) — useful for re-running a previously-failing delivery against a now-fixed receiver.
Returns 202 Accepted with { enqueued: true, original_id, replay_id }.