Webhooks
Pathbound can deliver any tracked event to your own URLs. Use webhooks to keep an external system in sync — fire a Slack message when a high-intent visitor identifies, push form submissions to your data warehouse, trigger a workflow when a contact’s lifecycle stage changes.
Subscribing
Section titled “Subscribing”In the dashboard:
- Open Settings → Webhooks and click New webhook.
- Pick the event type to subscribe to. Any event your tenant has ever recorded is selectable (page views, form submissions, custom
track()events, lifecycle changes, etc.). - Set the destination URL. It must be
https://and reachable from the public internet — local URLs and internal IP ranges are rejected. - Optionally add URL filters (AND / OR patterns) to scope deliveries — e.g. only deliver
page_viewevents whose URL contains/pricing. - Optionally add custom headers that should be sent with each request (encrypted at rest).
- Save. Copy the signing secret — it is shown once.
Each tenant can have up to 25 active subscriptions.
Delivery format
Section titled “Delivery format”Pathbound sends a POST to your destination URL with a JSON body and these headers:
Content-Type: application/jsonX-Pathbound-Signature: sha256=<hex>X-Pathbound-Event: page_viewX-Pathbound-Subscription: sub_abcPlus any custom headers you configured.
Example payload
Section titled “Example payload”{ "event": "page_view", "subscription_id": "sub_abc", "event_id": "evt_xyz", "tenant_id": "tenant_123", "timestamp": "2026-04-29T12:00:00.000Z", "data": { "url": "https://example.com/pricing", "domain": "example.com", "title": "Pricing — Example", "path": "/pricing", "visitorId": "vis_123", "internal_contact_id": "ct_abc", "contact": { "_id": "ct_abc", "firstname": "Ada" } }}The exact data shape varies by event type — it mirrors the response of GET /v1/events/:event_id plus any joined contact/company.
Verifying the signature
Section titled “Verifying the signature”Compute an HMAC-SHA256 of the raw request body (not the parsed JSON) using your subscription’s signing secret. Compare it to the value in X-Pathbound-Signature (after stripping the sha256= prefix). Use a constant-time comparison.
import crypto from 'node:crypto';
function verify(rawBody, header, secret) { if (!header?.startsWith('sha256=')) return false; const expected = crypto .createHmac('sha256', secret) .update(rawBody) .digest('hex'); const provided = header.slice('sha256='.length); if (provided.length !== expected.length) return false; return crypto.timingSafeEqual(Buffer.from(provided), Buffer.from(expected));}import hmac, hashlib
def verify(raw_body: bytes, header: str, secret: str) -> bool: if not header.startswith('sha256='): return False expected = hmac.new(secret.encode(), raw_body, hashlib.sha256).hexdigest() provided = header[len('sha256='):] return hmac.compare_digest(expected, provided)If you can’t access the raw body in your framework (e.g. Express that already parsed JSON), capture it before parsing — re-stringifying does not preserve byte-for-byte equality.
Retries
Section titled “Retries”Pathbound expects a 2xx response within 10 seconds. Anything else (timeout, 4xx, 5xx) is retried with exponential backoff:
| Attempt | Delay after previous |
|---|---|
| 1 | (initial delivery) |
| 2 | 1 minute |
| 3 | 5 minutes |
| 4 | 30 minutes |
| 5 | 2 hours |
| 6 | 12 hours |
After 6 failed attempts, the delivery is marked failed and not retried further. The subscription itself stays active.
A 2xx response is treated as success regardless of body. Non-2xx responses with body "ignore" (case-insensitive) are also treated as success — useful for filtering at the receiver without burning retries.
Inspecting deliveries
Section titled “Inspecting deliveries”The dashboard shows the recent delivery history per subscription — destination response code, latency, body, retry count. Replay a failed delivery from the dashboard with the Re-send button.
Best practices
Section titled “Best practices”- Treat the destination URL as public. Anyone with the URL can attempt delivery; the signature is your only authenticator. Verify it on every request.
- Process asynchronously. Acknowledge with
200 OKquickly, then process the event in a background job. Long synchronous handlers eat into the 10 s window. - Be idempotent. Pathbound delivers at-least-once. Use
event_idas a dedup key. - Log
X-Pathbound-Eventandevent_idso you can correlate with the dashboard delivery log.