Authorization header with the Push scheme, and the webhook secret is a separate value from the Webhooks Integration settings.
- The scheme is
Push, notBearer.Authorization: Push <key>is the credential, so a Bearer token is refused with a 401. That is the single most common way a first call fails. - Writes go to a different host. The Push API lives at
push.userlist.com, notapi.userlist.com, and every operation is a write. There is no way to read a user or a company back through this API. - Everything is queued. Every call answers
202 Acceptedwith an empty body: the payload has been accepted for processing, not applied. So each operation here resolves to an acknowledgement saying what was queued, not to the stored object. - Deletes carry a body. Removing a user, company or relationship is a
DELETEwith the identifier in a JSON body, which is how the Push API names what to remove. - A failure is
{ status, code, errors: [...] }. Note the array: the plugin keeps every message, so a422tells you each field that failed rather than only the first. - Rate and size limits. 1,000 requests a minute per account, and 100 KB per request body. A
429was refused before the write and is retried up to three times; a413cannot be fixed by retrying, so it is not. - A 5xx is never replayed. Since success only means “queued”, a server error may mean the write landed and the response was lost. Nothing is retried that could duplicate a user, event or message.
- Matching is by identifier or email.
users.upsertneeds at least one, and uses theidentifierwhen both are given. Topic preferences only change the topics sent; the rest keep their values. - Messages follow two sets of rules. With a
templatethe subject and body come from it; without one, both are required inline. Email goes to auseror atoaddress; a web message needs auserand must not carryto, since it is shown in-app. - Webhooks are verified with the webhook secret, not the Push key. Userlist sends
X-Userlist-Signature: t=<timestamp>,v1=<hex hmac>, signing"<timestamp>.<raw body>"with HMAC-SHA256. The plugin compares in constant time and rejects anything older than five minutes, so a captured request cannot be replayed. - Delivery is at-least-once and unordered. Userlist retries a failed delivery up to ten times with backoff, and does not guarantee order — so a
user_updatedcan arrive before theuser_createdfor the same person.
- 8 typed API operations
- 14 incoming webhook event types
Setup
1
Install
2
Add the plugin
corsair.ts
multiTenancy: true, scope calls with corsair.withTenant(id). See Quick start for KEK + Hub keys, and Multi-tenancy for account isolation.3
Choose authentication
- API Key (Recommended)
No setup required yet. When you make your first request as a tenant, Corsair prompts for the API key.More: API Key
4
Connect a tenant
Mint a connect link and send the tenant to it. Hub hosts the page and delivers the result to your app — see Connect / OAuth.
Example API calls
Record a subscription eventWebhooks
This plugin registers 14 webhook event types. Configure the provider to POST to your Corsair HTTP handler, then usewebhookHooks on the plugin factory.
See Webhooks for every event path and payload shape, and Webhooks concept for routing.
What’s next
API reference
Every
userlist.api.* operation with input and output types.Webhooks
Event paths, payloads, and
webhookHooks examples.Connect / OAuth
createLink, Hub delivery, and tenant connect flows.
Use with agents
Expose this plugin’s operations as MCP tools.