Skip to content

Webhooks service

/webhooks lets you subscribe HTTPS endpoints to device and step activity, so your systems learn about changes as they happen instead of polling devices and trails. It is the customer-facing counterpart to the internal callbacks service: callbacks drive Pantahub's own derived state, webhooks drive yours.

The service consumes the change stream behind the API, derives typed events from it, and POSTs each one to every matching subscription with an HMAC-SHA256 signature and retries.

For the end-to-end walkthrough — creating a subscription in the Hub, verifying a delivery, what to return — see the Webhooks guide.

Calls need a JWT from auth carrying one of the api, api.readonly, webhooks, webhooks.readonly or webhooks.write scopes. Read operations accept the read scopes; anything that mutates a subscription needs a write scope.

sh
TOKEN=$(curl -s -X POST https://api.pantahub.com/auth/login \
  -H 'Content-Type: application/json' \
  -d '{"username":"<user>","password":"<pass>"}' | jq -r .token)

Event catalog

sh
curl -s -H "Authorization: Bearer $TOKEN" \
  https://api.pantahub.com/webhooks/event-types

Returns every event type with a human description — the same list the Hub renders when you create a subscription:

device.created              device.updated              device.deleted
device.public.toggled       device.device_meta.updated  device.user_meta.updated
step.created                step.progress.changed       step.status.changed

Creating a subscription

sh
curl -s -X POST -H "Authorization: Bearer $TOKEN" \
  -H 'Content-Type: application/json' \
  https://api.pantahub.com/webhooks/ \
  -d '{
    "nick": "production-fleet",
    "url": "https://example.com/hooks/pantahub",
    "event_types": ["step.status.changed", "device.created"],
    "enabled": true
  }'

The response includes the generated signing secret. It is returned only on creation and on rotation — store it then, or rotate to get a new one.

Optional fields narrow what you receive: devices (a list of device PRNs), user_meta_keys and device_meta_keys (limit metadata events to changes touching those keys), custom_headers (sent with every delivery), and retry_policy.

Managing subscriptions

sh
# list
curl -s -H "Authorization: Bearer $TOKEN" https://api.pantahub.com/webhooks/

# one subscription
curl -s -H "Authorization: Bearer $TOKEN" https://api.pantahub.com/webhooks/<id>

# pause without deleting
curl -s -X PATCH -H "Authorization: Bearer $TOKEN" \
  -H 'Content-Type: application/json' \
  https://api.pantahub.com/webhooks/<id> -d '{"enabled": false}'

# delete
curl -s -X DELETE -H "Authorization: Bearer $TOKEN" \
  https://api.pantahub.com/webhooks/<id>

PUT replaces a subscription wholesale, PATCH merges the fields you send. A deleted subscription stops receiving events immediately.

Testing and rotation

sh
# send a synthetic delivery through the full signing + retry path
curl -s -X POST -H "Authorization: Bearer $TOKEN" \
  https://api.pantahub.com/webhooks/<id>/test

# rotate the signing secret; deliveries are signed with both during overlap
curl -s -X POST -H "Authorization: Bearer $TOKEN" \
  https://api.pantahub.com/webhooks/<id>/rotate-secret

Rotation keeps the previous secret as a secondary and signs every delivery with both, so receivers that check every v1, entry in the Webhook-Signature header keep working while you roll the new value out.

Delivery history

sh
# every attempt for one subscription (filter with ?status=failed)
curl -s -H "Authorization: Bearer $TOKEN" \
  https://api.pantahub.com/webhooks/<id>/deliveries

# one attempt, including the recorded request and response
curl -s -H "Authorization: Bearer $TOKEN" \
  https://api.pantahub.com/webhooks/<id>/deliveries/<did>

# resend a single attempt
curl -s -X POST -H "Authorization: Bearer $TOKEN" \
  https://api.pantahub.com/webhooks/<id>/deliveries/<did>/replay

Each row records the attempt number, status, the HTTP response code and duration, and the scheduling timestamps — enough to tell a receiver that timed out from one that rejected the payload.

Events

The /webhooks/events* family is the account-wide log: every event derived for you, whether or not a subscription matched.

sh
# recent events (?type=, ?resource_prn=, ?since=, ?until=, ?limit=, ?skip=)
curl -s -H "Authorization: Bearer $TOKEN" \
  "https://api.pantahub.com/webhooks/events?limit=50"

# one event, including its full payload
curl -s -H "Authorization: Bearer $TOKEN" \
  https://api.pantahub.com/webhooks/events/<eid>

# every delivery of that event, across all subscriptions
curl -s -H "Authorization: Bearer $TOKEN" \
  https://api.pantahub.com/webhooks/events/<eid>/deliveries

# fan a fresh attempt out to every matching subscription
curl -s -X POST -H "Authorization: Bearer $TOKEN" \
  https://api.pantahub.com/webhooks/events/<eid>/redeliver

Fetching an event by id doubles as an authenticity check: events are scoped to the caller's owner, so an id you did not receive — or one belonging to another account — returns 404. That makes GET /webhooks/events/{eid} a useful second line of defence before acting on a high-value delivery.

Delivery semantics

Deliveries are at-least-once and unordered. A 2xx completes an attempt; 408, 425, 429 and 5xx are retried with exponential backoff (8 attempts from 30 s by default, doubling); any other 4xx is treated as permanent and dead-lettered without retry. Each attempt must finish within 10 seconds.

Receivers should dedupe on the event id, which is stable across retries, rather than request_id, which is per attempt.

Endpoint summary

EndpointPurpose
GET /webhooks/event-typesCatalog of event types
GET /webhooks/List subscriptions
POST /webhooks/Create a subscription
GET /webhooks/{id}Read one subscription
PUT / PATCH /webhooks/{id}Replace / merge a subscription
DELETE /webhooks/{id}Delete a subscription
POST /webhooks/{id}/rotate-secretRotate the signing secret
POST /webhooks/{id}/testSend a synthetic delivery
GET /webhooks/{id}/deliveriesDelivery log for a subscription
GET /webhooks/{id}/deliveries/{did}One delivery attempt
POST /webhooks/{id}/deliveries/{did}/replayResend one attempt
GET /webhooks/eventsAccount-wide event log
GET /webhooks/events/{eid}One event with payload
GET /webhooks/events/{eid}/deliveriesDeliveries of an event
POST /webhooks/events/{eid}/redeliverRedeliver to all matching subs