architecturesaas

The right way to build multi-tenant webhook delivery

March 25, 2026 · 11 min read

Most SaaS products bolt on webhooks after the fact: one global endpoint list, flat event types, and customers who have to call support to add a new URL. This works for 10 customers. It breaks at 1000.

The consumer model

The right model is consumer-scoped: each of your customers is a "consumer" in your webhook system. Consumers own their own endpoints, subscribe to their own event types, and manage their own webhook secrets. You never have to field a "can you add my webhook URL?" support ticket again.

In HookSync, you create a consumer when a customer first appears in your system:

// Create or update consumer (idempotent) POST /api/v1/organizations/{org_id}/consumers { "uid": "customer_123", // your internal customer ID "name": "Acme Corp", // display name "metadata": { "plan": "pro" } }

Then when your backend sends an event, include the consumer UID in the ingest call. HookSync routes the event only to that consumer's endpoints:

POST /ingest/{org_id} { "event_type": "order.placed", "consumer_id": "customer_123", "payload": { "order_id": "ord_456" } }

The portal token pattern

Now the interesting part. Your customer wants to manage their own endpoints. You have two options: build a webhook settings UI in your product, or give them a portal token and let them use HookSync's portal API.

The portal token approach: your backend generates a short-lived token and passes it to your frontend. The frontend uses it to call the portal API directly — creating, editing, and deleting endpoints on behalf of the customer. No your-side UI needed.

// Your backend: generate token for this customer session const { token } = await hooksync.consumers.generatePortalToken('customer_123'); // Your frontend: use it to manage their endpoints const { data: endpoints } = await fetch( 'https://api.hooksync.net/portal/v1/organizations/{org_id}/endpoints', { headers: { Authorization: `Bearer ${token}` } } ).then(r => r.json());

Isolation guarantees

Portal tokens are scoped to a single consumer. A compromised token for customer A cannot read or write customer B's endpoints. The isolation is enforced at the database level — every query is scoped by consumer_id.

Tokens are short-lived (1 hour default, configurable). If a token leaks, the blast radius is limited to one customer for one hour.

Design it in from day one

The mistake is building a flat, org-wide webhook system and trying to layer per-customer isolation on top. It never works cleanly. If you are building a B2B SaaS product and expect to offer webhooks to customers, model consumers from day one — even if only you use the system for the first six months.

← back to blog