Skip to main content
Webhooks let your systems react to events in Sotto in real time. You subscribe to a topic by registering a callback address; when a matching event occurs, Sotto sends an HTTP POST to that address. Manage subscriptions with the Webhooks endpoints (create, list, retrieve, and update).

Topics

You can subscribe to the following topics:
A brand may register multiple webhooks, and the same address may subscribe to the same topic more than once. In non-development environments, callback addresses must use HTTPS.
user/create means the user record was created in Sotto, not that the person is new to your business. Importing a customer who already exists elsewhere still produces user/create the first time Sotto sees them.

Your own writes do not come back to you

When you change a user through the API, Sotto does not send you a callback for that change. This applies to all of your subscriptions on that topic, not just one: if you have registered two addresses for user/update, neither is called for a change you made yourself. Other partners are unaffected and still receive it. This keeps a write from bouncing straight back into the system that made it. You still receive callbacks for changes made by anyone else, including your brand’s own users in the Sotto portal. One case looks like an exception and is not. If you create a user through the sign-up endpoint, the consent that Sotto records for them is its own event, so you do receive user/opt-in for a user you just created. That callback describes the consent outcome rather than replaying your write.

Callback requests

Sotto delivers events as a POST to your registered address. Callbacks are signed with the same HMAC scheme as API requests (see Authentication) and include these additional headers:
string
The API version for the callback (e.g. v1).
string
The UUID of the webhook subscription that triggered this callback.
string
The topic of the event (e.g. user/opt-in).

Payload

The body contains the time the event occurred, the topic that produced it, and the associated resource:
string
When the event occurred, in UTC.
string
The topic that produced this event, matching the x-bigco-topic header. The two can never disagree, so you can route on whichever is more convenient. If you queue or store callback bodies without their headers, route on this.
string | null
The lifecycle change behind the event: create, update, opt_in or opt_out. Useful if you want one handler across the user/* topics. It is null on topics that describe no lifecycle change, such as user/tap and ticket/updated, so it is not a substitute for topic.
object | null
The tap behind the event. Populated on user/tap and null on every other topic. The key is always present, so the body keeps one shape.
object
The resource the event is about. For user/* topics this is a User object.
For user/* topics, data is a User object.
A brand can turn off custom data or tap location for an individual subscription. With custom data off, custom_data arrives as null on every user/* topic for that subscription; tap location is covered under Taps. Either change applies from the moment the brand makes it, including to retries of events first sent before it. The keys are always present, so check for null rather than for a missing key.

Taps

On user/tap, the same User object arrives in data and the tap itself rides beside it in tap:
device is null for a plain QR scan with no device behind it, and location is null when the device has none recorded or when the brand has turned off location for this subscription. labels is an empty array when the device carries none. As with tap itself, these keys are always present, so check for null rather than for a missing key. labels carries your own taxonomy. canonical is the key:value form, so you do not need a second lookup to match on it.
device.tap_count is the device’s lifetime tap count, across everyone who has ever tapped it. It is not the number of times this user has tapped. Read it the wrong way and you get numbers that look entirely plausible and are wrong.

Responding

Your endpoint must return a 2xx status code within 10 seconds. Any other response, or a timeout, is treated as a failure and the callback is retried up to 20 times before being dropped. Sotto waits about 10 minutes between attempts, so a subscriber that is down for a short window catches up on its own once it recovers. Sotto does not follow redirects. A 3xx response counts as a failed delivery, the same as a 5xx, so register the final address your endpoint serves from. An endpoint that redirects, for example to add a trailing slash or move to a new host, fails every delivery and is eventually paused.

Paused subscriptions

An endpoint that keeps failing is not retried forever. When three events in a row are dropped after exhausting their retries, Sotto pauses the subscription: no further callbacks are attempted, and events that occur while it is paused are not queued for later. One successful delivery in between resets the count, so an outage shorter than the retry window (about three hours and twenty minutes) never pauses anything. A paused subscription is not abandoned. Sotto probes it with one new event, no sooner than ten minutes after the pause or the previous probe. A probe needs an event to carry it, so on a quiet topic the next probe waits for the next event. If your endpoint answers 2xx, the subscription resumes on its own and normal delivery, with retries, continues from that event. If the probe fails it is not retried, and the subscription stays paused until the next probe. So once your endpoint is healthy again, deliveries resume with the first event after the ten-minute interval, without any action from you. To resume sooner, or after moving to a new URL, update the subscription with a new address using the update endpoint. Repointing it clears the pause immediately; re-saving the same address does not. Events still queued or retrying when you change the address are delivered to the new address, signed for it, rather than to the old one. The brand can also resume it, or switch a subscription off and on, from their side.
Events that occurred while a subscription was paused are not replayed when it resumes, and the probes in between are single attempts: a probe that fails is gone. If your endpoint has been down for long enough to be paused, reconcile against the Users endpoints rather than waiting for callbacks that will not come.

Best practices

Your endpoint may receive the same event more than once. Retries are the common cause, but a delivery can also be repeated if Sotto restarts partway through fanning an event out to several subscribers. Use x-bigco-webhook-id together with date_occurred to detect and safely ignore duplicates.
Always validate the HMAC signature on incoming callbacks before acting on them, and serve your endpoint over HTTPS.
Queue the event and return 200 immediately rather than doing heavy work inline. This keeps you within the 10-second window and avoids unnecessary retries.