Skip to main content
Register HTTPS endpoints and Peeve posts events to them as things happen — a hand-off opens, a lead is created, a meeting is booked.
This is Peeve sending to you. It is not the inbound connector plumbing for WhatsApp or Slack, which Peeve configures on your behalf and which is not a customer surface.

Setup

Two equivalent paths — Connectors → Webhooks in the dashboard, or the API. An endpoint created through the API is the same row with the same guarantees, and it flips the Connectors card to connected just the same. Peeve issues a signing secret (whsec_…) when you create the endpoint.
The secret is returned exactly twice: on create, and on rotate-secret. Never on a list, a GET or a PATCH, and there is no way to recover it later. Store it when you see it.Reads return a 4-character secret_hint instead, so you can confirm a rotation happened without exposing the secret.
Choosing events is mandatory. POST /v1/webhooks requires events; omitting it is a 400 naming the field, not a subscription to everything. An explicit events: [] is refused rather than widened — in an API call an empty array is a deliberate statement, and silently turning it into “all events” would hand the maximum traffic to a caller who meant to narrow.Pass ["*"] if you genuinely want everything, including events added later.channels is different, and deliberately so. It is optional, and an absent value means every channel — because a channel filter can only ever narrow an event set you already chose. An absent channels cannot deliver an event nobody asked for; an absent events could deliver all of them.That is the whole asymmetry: handoff.* payloads carry real customer message text, so subscribing a new URL to them because a field was left blank is not a convenience.
Use the test-send to confirm your receiver works before you rely on it. A test delivery carries livemode: false and a Peeve-Test: true header, and is capped at 20 per endpoint per hour.
Never create real records from a delivery with livemode: false. Log it and move on.

Verifying a request — read this first

Verify before you trust. A POST to your URL is unauthenticated by default: anyone who learns the URL can forge a hand-off. A receiver that acts on one — opens a ticket, pages an on-call, issues a refund — has been had.The signature is the only thing that makes a request provably from Peeve.
The signed string is "<timestamp>.<raw body>". The timestamp is inside the HMAC, so a captured request cannot be replayed with a rewritten header. Three rules:
  1. Sign the raw body bytes, exactly as received. Re-serialising parsed JSON produces different bytes and the check will fail.
  2. Compare in constant time.
  3. Reject anything older than about five minutes.

The envelope

Headers

Event catalogue

Status and channel are payload fields, never event names. There is no handoff.resolved event — it is handoff.status.changed with status: "resolved". Match on type, then read the field.

Status vocabularies

Retries and idempotency

Every retry carries the same id. Record the ids you have processed and ignore repeats — that is the whole idempotency contract.Answer 2xx fast and do your work afterwards. A receiver that finishes its processing before responding will be retried while it is still working.

Delivery log

Connectors → Webhooks → your endpoint → Recent deliveries. Each row records the event, the channel, the outcome, your HTTP response code, the attempts taken and how long it took.

Managing endpoints

There is no third lifecycle state; this matches the connector UI’s two operations exactly.Reach for rotate-secret, not delete-and-recreate, when a secret leaks — recreating changes the endpoint id and throws away its delivery history.

Destination requirements

Enforced on update as well as create, so an endpoint cannot be edited into a private address:
  • HTTPS onlyhttp:// is a 400.
  • No localhost, no RFC-1918 private ranges, no IPv6 loopback, no link-local 169.254.169.254, no .internal, no non-HTTP schemes.
Worth knowing before you start: a local tunnel will not work unless it terminates on a public HTTPS address. An endpoint whose URL later stops qualifying is marked blocked in the delivery log.