Synq Docs
Reference

Webhook events

Every event a webhook subscription can receive, and how deliveries are signed and retried.

A webhook subscription receives the events it asks for as JSON POSTs. Each event is a row of the org's audit log: type names what happened, and data carries the row (its id, the org and brand, who did it, the resource and the details in metadata). The management API reference shows the exact payload.

Sent to each subscription that wants it, with webhook-id, webhook-timestamp and webhook-signature (Standard Webhooks). Answer 2xx within 10 seconds; anything else is tried again, 6 times in all.

EventResourceWhat happened
org.createdorgSomeone made an org, and its default brand: they own it.
org.updatedorgAn org's name, slug or settings changed.
org.deletedorgAn org was deleted: purged 30 days later.
org.plan_changedorgAn org moved to another plan (metadata.from, metadata.to).
org.disabledorgSynq's staff disabled the org, metadata.reason saying why: nothing serves it (no route but reading it, no key, no issuer, no webhook) until they enable it again. Nothing is deleted.
org.enabledorgSynq's staff enabled a disabled org again (metadata.reason): everything serves it again.
brand.createdbrand
brand.updatedbrandA brand's name, branding or sign-up switch changed.
brand.deletedbrandA brand was deleted: its issuer stopped at once; purged 30 days later.
brand.reply_to_confirmedbrandSomeone who reads a brand's reply-to address confirmed replies to its users' email may come there, through the link Synq emailed it: metadata.reply_to. Setting it is brand.updated.
brand.app_registrations_fullbrandThe brand holds as many apps that registered themselves (MCP hosts, agents) as Synq keeps for one brand, metadata.limit, so its issuer refuses new registrations until unused ones are removed. At most one an hour per brand.
app.createdapp
app.updatedappAn app's name, URIs, links, settings, or whether it is disabled, changed.
app.secret_rotatedappAn app got a new secret; the old one works until previous_secret_expires_at.
app.deletedapp
app.api_access_changedappWhich of an API's scopes an app may ask for changed (metadata.api).
app.refresh_token_replayedappA refresh token the app had already rotated came back inside the app's reuse window and got its rotation's tokens again, instead of ending the grant: metadata.user, whose grant it is (the actor too), and metadata.elapsed_seconds since the rotation. Many of them may mean the app's servers race, or a token was stolen with the app's secret.
api.createdapi
api.updatedapiAn API's name, scopes, token lifetime, third-party switch or own client changed.
api.deletedapiAn API was deleted: requests for it fail with invalid_target from then on.
org.ownership_transferredmemberAn org's owner handed it to another member, who is now its owner.
member.addedmemberSomeone joined the org (metadata.by: how, such as an invite).
member.role_assignedmember
member.removedmember
member.leftmemberA member left the org of their own accord.
role.createdroleOne of the org's own roles was made, changed or deleted.
role.updatedrole
role.deletedrole
invite.createdinviteAn invitation was sent, revoked, or accepted by the person it was for.
invite.revokedinvite
invite.acceptedinvite
webhook.createdwebhookA webhook subscription was made, changed, had its secret rotated, or was deleted.
webhook.updatedwebhook
webhook.secret_rotatedwebhook
webhook.deletedwebhook
webhook.disabledwebhookSynq turned a webhook off after its deliveries kept failing.
token_gate.createdtoken_gateA token gate was made, changed (its name, rule, schedule, message, whether it is on, or where it applies) or deleted: what its rule is made of (how many conditions of each kind, how deep, on which chains, how many addresses its allowlists hold), never the addresses.
token_gate.updatedtoken_gate
token_gate.deletedtoken_gate
email_domain.createdemail_domainA sending domain was added for the org's brands' email: metadata.domain. Synq registers it with Postmark and shows the DNS records to publish.
email_domain.verifiedemail_domainA sending domain verified (metadata.domain): its records resolve and Postmark agrees, so its brands' email comes from it. Synq is the actor.
email_domain.failedemail_domainA verified sending domain stopped verifying (metadata.domain, metadata.problem): two checks in a row missed a record, and its brands' email comes via Synq until it is back. Synq is the actor.
email_domain.deletedemail_domain
brand.custom_domain_addedcustom_domainA brand's custom domain was added: metadata.hostname. It waits for its CNAME (metadata.cname_target).
brand.custom_domain_verifiedcustom_domainIts CNAME pointed at its own target: the tenant controls the name (metadata.hostname).
brand.custom_domain_activatedcustom_domainIts certificate is live and its issuer answers at https://<hostname> (metadata.issuer).
brand.custom_domain_failedcustom_domainIt failed (metadata.problems: the codes, such as verification_expired or cloudflare_blocked): it isn't served, and the tenant can remove it and add it again.
brand.custom_domain_suspendedcustom_domainIts org's plan lost branding.custom_domain: it is still served until metadata.grace_ends_at, then removed unless the plan gets it back.
brand.custom_domain_restoredcustom_domainIts org's plan got branding.custom_domain back in the grace period: active again.
brand.custom_domain_removedcustom_domainIt was removed and no longer answers (metadata.reason: requested, plan_lapsed, dns_failing, verification_expired, brand_deleted, org_deleted or abuse); apps that use its issuer stop working.
api_key.createdapi_keyAn API key was made: its name, prefix, permissions and limits, never the key.
api_key.revokedapi_key
change_request.createdchange_requestA member asked from an MCP host for a change that waits for their approval in the dashboard: its kind, the app and what it sets; then approved (the change itself is its own row, app.updated) or declined, each naming the host's app as asked_from.
change_request.approvedchange_request
change_request.declinedchange_request
file.uploadedfileAn upload was checked, re-encoded and kept: metadata.purpose (brand_logo, theme_background), what was kept (content_type, size, width, height, sha256). What it is for changes in its own row (brand.updated).
file.rejectedfileAn upload wasn't what its purpose takes, and nothing was kept: metadata.reason (type, size, dimensions, unreadable) and what the upload said it was (declared_type, declared_size).
file.downloadedfileA short-lived link to a private file was handed out (metadata.purpose, expires_in seconds). None of Synq's files is private yet.
user.createduserSomeone signed up: the brand's first sign-in with a new identity.
user.updateduserSomeone changed their own profile, through the account API.
user.looked_upuserSynq's staff looked users of the brand up, for support: metadata.users (their ids), metadata.query and metadata.reason. The actor is the staff member.
user.email_verifieduserA user's email address was proved theirs (metadata.by: how). Signing in with an email code to an account whose address nobody had proved removes what its maker could sign in with (metadata.methods_removed) and ends its sessions.
user.password_resetuserA user chose a new password with a reset link from their email; every session and grant ended (metadata.sessions_ended).
user.passkey_addeduserA user made a passkey: metadata.passkey its id, metadata.synced whether it can leave the device, metadata.by where (sign_up, sign_in when an app asked for one after a sign-in, account_api).
user.passkey_removeduserA user's passkey was removed (metadata.passkey, metadata.by).
user.identity_linkeduserA way to sign in joined a user's account (metadata.provider; metadata.by: how): a provider whose word on the email the brand takes vouched for the address the user had verified (verified_email,), or the person added it, signed in again (link,).
user.identity_unlinkeduserA way to sign in left a user's account (metadata.provider, metadata.identity; metadata.by: who),.
user.consent_granteduserA user allowed an app that isn't the brand's own something they hadn't before: metadata.client_id, and what this answer added (metadata.scopes, metadata.claims, metadata.apis: each API's identifier and scopes). Later sign-ins to it don't ask again for what they allowed.
user.consent_revokeduserWhat a user had allowed an app was withdrawn (metadata.client_id; metadata.by: account_api for the person, tenant): the app's grants and tokens for them ended, and their next sign-in to it asks again.
user.api_key_createduserA user made an API key for one of the brand's APIs: metadata.key_id its id, metadata.prefix, metadata.api the API's identifier, metadata.scopes and metadata.expires_at. Never the key.
user.api_key_revokeduserA user's API key was revoked (metadata.key_id, metadata.prefix, metadata.api; metadata.by: account_api for the person, tenant). Revoking an API's keys all at once, by no longer letting its users make them, is recorded once, as the API's api.updated.
user.sessions_revokeduserSessions ended, and with them their grants (metadata.sessions: how many).
user.deletion_scheduleduserSomeone asked for their account to be deleted, 30 days on.
user.deletion_cancelleduser
user.deleteduserA user was deleted for good, 30 days after it was asked for: only their id remains.
user.sign_in_faileduserA sign-in that didn't finish: the method refused what it was given, or the pipeline refused the person (metadata.reason). Never the secret.
user.token_gate_checkeduserA sign-in to an app with token gates, or the app refreshing its tokens, checked what the user holds: metadata.at is sign_in or refresh, and metadata.result passed, refused (on a refresh, the app's grant ended) or unavailable (what decides it couldn't be read: a sign-in was refused, a refresh asked to try again), with each gate's id, its result (passed, refused, unavailable, not_in_effect outside its window, closed) and the paths of its conditions not met or not read, how many reads it cost and how many answers were kept, and the app as act_client_id. Never the wallets or what they hold.
user.token_gate_dry_runuserA member, or an org's key, tested a token gate or a draft rule against a user's wallets in a brand with POST /v1/orgs/{org}/token-gates/check: the gate (or that it was a draft) and its result. Never the wallets or what they hold. A test against wallet addresses alone reads only public chain data, and isn't recorded.
webhook.testwebhookA test delivery you asked for (POST /v1/orgs/{org}/webhooks/{webhook}/test), signed like any other.