POST to a URL you register, on the day a scan finishes.
Webhooks are for platform-partner keys only. An organisation key gets
403 partner_only on every webhook route. Every call that creates, changes or deletes something also needs the key’s write scope. Without it the answer is 403 write_scope_required. See Authentication.- An endpoint is an HTTPS URL of yours, plus the secret Surfais signs its requests with.
- A subscription tells an endpoint which event type to receive, for which organisations and own brands, and at what threshold.
- A delivery is one event queued for one endpoint. It keeps the attempt count and the outcome of the last attempt, so you can inspect it and replay it.
scan.completed, score.threshold_crossed, sentiment.threshold_crossed and the webhook.test verification ping.
Set up an endpoint
1
Register an endpoint
Call Replace the URL with your own. It must:
POST /webhook-endpoints with your receiver’s url and an optional description.cURL
- use
https - contain no credentials (no
user:password@) - resolve to a public address
400 validation_error, and details[].code names the rule: url_invalid (not an absolute URL), url_not_https, url_has_credentials or url_not_public. A private or cloud-metadata address is url_not_public. So is a hostname that does not resolve in time.The body is strict. Any field other than url and description is refused with 400 validation_error, so you cannot choose your own secret.The 201 response carries the endpoint and its signing secret.The secret is whsec_ followed by 43 URL-safe characters: letters, digits, - and _.2
Subscribe to events
Call Each call answers You can subscribe before the endpoint is verified. Nothing is sent for a subscription until its endpoint is
POST /webhook-endpoints/{endpointId}/subscriptions once per event type. Send event_type, an optional org_id and brand_id to narrow the scope, and the thresholds the event type takes. This body is strict too.cURL
201 with the subscription:active. The rules for each event type are in Subscription rules.3
Verify the endpoint
A new endpoint starts in The Surfais sends a signed
pending_verification and receives only test pings. Deploy your receiver, make sure it verifies signatures, then ask for a ping:cURL
202 response means the ping is queued, not that it has arrived:webhook.test event to your URL. When your receiver answers with a 2xx, the endpoint becomes active. Read status on GET /webhook-endpoints/{endpointId} to confirm, or look the ping up in the delivery log.Real events are only queued for active endpoints. A ping that fails leaves the endpoint in pending_verification. Enough failed pings in a row make it failing, like any other endpoint, and the fix is the same: a ping your receiver answers with a 2xx. The failed ping is retried like any other delivery, and every call to POST …/test sends a new ping with its own event id.Endpoint states
status on an endpoint is one of:
A
failing endpoint recovers by itself when one of its queued retries gets a 2xx. To recover sooner than the next retry, or when nothing is left in the queue, send a test ping.
GET /webhook-endpoints/{endpointId} also returns consecutive_failures (failed attempts since the last success, across all deliveries to the endpoint), last_success_at and last_failure_at.
Subscription rules
Each event type takes different threshold fields. A body that breaks a rule is refused with400 validation_error, and details[].code names the rule.
webhook.test cannot be subscribed to. It is sent only by POST …/test.
min_delta is the smallest move that fires, inclusive, in the subscription’s direction. sentiment_floor fires when mean sentiment falls below the floor. Threshold semantics has the detail.
Scope
- No
org_id: every organisation linked to your partner account. The set is worked out again at each scan, so a newly linked organisation is included and a revoked one stops at once. org_id: one organisation. It must be linked to your partner account. Anything else is404 not_found. The API never confirms that an organisation exists.brand_id: one own brand of that organisation. An id that is not an own brand oforg_idis404 not_found, and that includes a competitor’s id.brand_idneedsorg_id. Without it the answer is400 validation_errorwithdetails[].codeset toorg_id_required.
List and delete subscriptions
GET /webhook-endpoints/{endpointId}/subscriptions lists an endpoint’s subscriptions. DELETE /webhook-endpoints/{endpointId}/subscriptions/{subscriptionId} answers 204. Deliveries already queued from a deleted subscription are kept and still sent. Their subscription_id becomes null.
Rotate the secret
POST /webhook-endpoints/{endpointId}/rotate-secret replaces the secret immediately. The 200 response has the same shape as the 201 above and carries the new secret, shown once.
cURL
Change a URL or delete an endpoint
There is no update route. To change a URL, register a new endpoint, give it the same subscriptions, verify it, and then delete the old one. While both are active, each receives its own delivery of every event, with the same event id, so dedupe on the event id.DELETE /webhook-endpoints/{endpointId} answers 204. It is permanent. It removes the endpoint’s subscriptions and its delivery history with it.
GET /webhook-endpoints lists your endpoints. It never returns a secret. Another partner’s endpoint, subscription or delivery id is 404 not_found, the same answer as an id that never existed.
Next steps
Verify signatures
The signing algorithm, with complete Node.js and Python receivers.
Webhook events
The envelope, each event’s
data, and how thresholds are judged.Delivery, retries and replay
At-least-once delivery, the retry schedule, the delivery log and replay.
Idempotency
Retry a write safely, including a secret rotation.