Events
An event is sent to your instance when the user has signed in to it, your webhook subscribes to that event, and the user granted the scope the event needs. Events are only sent for users within your plan’s user limit.Payload
Every delivery is aPOST with a JSON body.
string
Event id. The same on every retry, deduplicate on it.
string
Event type, for example
wallet.verified.string
When the event happened (ISO 8601).
boolean
True for events of a sandbox instance.
object
Event data. Always contains
sub, the user’s id, plus the fields below.thesis.created carries the new thesis in data.thesis, the same object as in List theses:
thesis.created
thesis.reply carries the reply in data.reply. thesis.reaction carries the post and the change:
thesis.reaction is only sent when the reaction actually changed, on is true for added and false for removed.
consent.revoked carries a reason: user_removed_access (the user removed your app), blocked_by_platform (you blocked the user in the dashboard) or account_deleted (the user deleted their CabalSpy ID). Delete the user’s data you got from CabalSpy ID when you receive it. Their refresh tokens are already revoked.
Headers
Verify the signature
The signature is an HMAC-SHA256 over<webhook-id>.<webhook-timestamp>.<raw body>, keyed with the base64-decoded part of your secret after whsec_. Always verify against the raw request body, and reject deliveries whose timestamp is more than 5 minutes off.
Delivery and retries
Webhook URLs must usehttps and resolve to a public address. Answer with any 2xx status within 10 seconds, ideally right away, and do the work afterwards. Redirects are not followed.
Any other answer, a timeout or a connection error is retried after 30 seconds, 2 minutes, 10 minutes, 30 minutes, 1 hour, 3 hours, 6 hours, 12 hours and 24 hours, about two days in total. After the last attempt the delivery is marked failed. The dashboard shows the recent deliveries of your instance with their status.
Deliveries can arrive more than once and out of order. Deduplicate on webhook-id and, when order matters, compare created_at.
Rolling the secret
When you roll the secret in the dashboard, the old secret stays valid for a while. During that time every delivery carries one signature per active secret inwebhook-signature, separated by spaces, so both your old and new deployment accept it.
