Skip to Content

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" | jq

Returns 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 }' | jq

The 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

FieldTypeDescription
namestring (1–100)Display name for the admin UI
urlstring (≤2048)Receiver URL. Validated against the SSRF blocklist unless WEBHOOK_ALLOW_PRIVATE_URLS=true
eventsarraySubscribed event types (subset of the catalog)

Optional fields

FieldTypeDefaultDescription
activebooleantrueInactive webhooks are skipped at fire time
filterobject{}Dot-path predicate; AND of all keys; arrays = “any of”
templatestringnull${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 }' | jq

Same 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" }' | jq

event_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 paramDescription
statusFilter by pending, success, or failed. Omit for all.
page1-based, default 1
limitDefault 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" | jq

Returns 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" | jq

Works 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 }.

Last updated on