How to build a multi-tenant webhook service
A multi-tenant webhook service delivers events for many customers from a single system while keeping each customer's configuration and failures separate. The tenant, not the endpoint, is the unit of isolation: every event, subscription, signing secret, retry schedule, and delivery log belongs to exactly one tenant and must never be visible to another.
Start from the tenant, not the endpoint
The data model decides how much of this is hard later. Give each customer a container, often called an application or a workspace, and hang everything else off it. An endpoint belongs to one tenant, a subscription pairs that endpoint with an event type, and every event you publish carries the tenant it was generated for. Sending then becomes a lookup inside one tenant rather than a query across all of them.
The failure mode worth designing against is a query that forgets the tenant filter and delivers one customer's order event to another customer's endpoint. That is a data breach, not a bug, so make the tenant ID part of the primary key or a required parameter on every path that reads endpoints. Passing the tenant through explicitly beats relying on a session variable that a background worker will not have.
Stopping one tenant from starving the others
Once a few hundred tenants share a worker pool, the noisiest one sets everyone's latency. A customer who imports a million records generates a million events, and if deliveries come off a single queue in arrival order, every other tenant waits behind that import. The same happens in reverse when a tenant's endpoint starts timing out: workers sit blocked on a receiver that no longer answers.
The fix has two halves. Per-endpoint isolation gives each delivery its own retry state so one broken receiver cannot hold up the rest, which is the webhook fan-out problem and applies whether or not you are multi-tenant. Per-tenant fairness is the part multi-tenancy adds: cap how much of the pool one tenant can occupy at once, and schedule work so a tenant with ten pending deliveries is not stuck behind a tenant with a hundred thousand. A queue per tenant makes that easy to reason about but expensive at scale, so most systems use a shared queue with a round-robin over tenants and a concurrency limit per tenant, plus a rate limit per endpoint for receivers that cannot absorb bursts. Endpoints that fail long enough should be paused by a circuit breaker instead of retried forever on someone else's budget.
Secrets and self-serve registration
Each endpoint needs its own signing secret so a webhook signature proves the request came from you and was not replayed against a different tenant. Sharing one secret across a customer base means any customer can forge deliveries to any other, and rotating it becomes an announcement rather than an API call. Support rotation from the start, with an overlap window where both the old and new secret verify, because customers will not all redeploy on your schedule.
Registration is the other place tenancy leaks. If customers add their own endpoint URLs, the API that accepts them runs inside your network with a destination the customer controls, which is a textbook SSRF vector. Require HTTPS, resolve the hostname and reject private and link-local ranges, and re-check at delivery time rather than only at registration, since DNS can change afterwards. Scope the registration endpoint to the calling tenant so nobody can subscribe to event types they do not own.
Routing inbound events to the right tenant
Multi-tenancy also applies when you receive webhooks on behalf of many customers, for example one OAuth app registered with a provider that sends you events for every connected account. Manual per-customer setup does not scale past a few dozen, so use a single receiving URL and map the provider's account identifier in the payload to your tenant, keeping that mapping keyed on the identifier you stored during the OAuth handshake. Verify the signature with the credential belonging to that provider account, then queue the event with the tenant attached so downstream work cannot pick the wrong one. Events for accounts you no longer recognize should be logged and dropped rather than processed against a guess.
What every tenant needs to see
The support cost of webhooks is mostly people asking why a delivery did not arrive, and the only good answer is a place customers can look themselves. Each tenant needs its own delivery log showing attempts, response codes, and payloads, the ability to replay a failed delivery, and a way to see that their endpoint is currently disabled and why. Building that means per-tenant authentication into a portal, retention rules for payload storage, and enough context in each record to explain a failure without your engineers reading production logs. Our guides to webhook monitoring and webhook debugging cover what those records should contain.
Building it or buying it
Written out, a multi-tenant webhook service is a tenant-scoped data model, a fair scheduler, per-endpoint retries and dead-lettering, secret rotation, SSRF protection, and a customer-facing portal. Each piece is tractable and the set of them is a product, which is why this is usually the webhook infrastructure teams underestimate. Building it makes sense when webhooks are how your product makes money. Otherwise Svix gives you the tenancy model, fan-out, retries, signatures, and a per-tenant app portal behind an API call, and our notes on why webhooks as a service lay out the tradeoff. If you would rather build, start with building a webhook sender.
Frequently asked questions
Should each tenant get its own webhook queue?
Only if the tenant count stays small. A queue per tenant makes isolation obvious but multiplies broker overhead and idle consumers. Most systems share a queue and add a per-tenant concurrency cap with round-robin scheduling, which gives similar fairness at a fraction of the operational cost.
Can tenants share a webhook signing secret?
No. A shared secret lets any customer forge deliveries that another customer will accept as authentic, and it makes rotation a coordinated migration. Generate a secret per endpoint, and support an overlap window during rotation so both the old and new secret verify.
How do you route an inbound webhook to the right tenant?
Map the sending provider's account identifier from the payload to the tenant you stored when that account was connected, then verify the signature with that account's credential. Avoid per-customer receiving URLs, which require manual setup for every new tenant.