Retour au blog
Partner Playbook

Multi-tenant returns: webhook isolation done right

Benjamin HayesJune 10, 20266 min de lecture
Multi-tenant returns: webhook isolation done right

Multi-tenant webhook isolation means every event a shared returns platform emits reaches exactly one tenant, signed with that tenant secret, and never carries or exposes another account data. On areturnz, a partner can resell one returns processing network to many brands or sub-accounts, so the whole model lives or dies on isolation: sub-accounts must never see each other, and return.received, return.graded, return.dispositioned, and return.restocked events must fire to the right tenant endpoint only. This post walks through each isolation layer an engineer needs to get right, the failure modes when you get it wrong, and how to verify and replay events.

Why isolation is the whole product

If you are building a resellable returns layer, isolation is not a feature you add later. It is the substrate. A partner (a 3PL, platform, or marketplace) onboards brands underneath their account, bills them, and hands each one a dashboard and an API token. The moment brand A can query, see, or receive a webhook for brand B parcel, the product is unsellable and probably in breach of contract. That is the difference between a shared platform and a leaky one. If you are shaping the commercial side of this, our note on reselling returns under your own brand covers the packaging; here we cover the plumbing.

Isolation is enforced in five layers. Skip one and the others do not save you.

The five isolation layers

1. Query-level tenant scoping

The foundation is that every database read and write is scoped to the caller account before it touches a row. On areturnz this is not left to the application developer to remember in each handler; the tenant id (and optional sub-account id) is extracted from the authenticated token and injected as a mandatory filter on every query. There is no code path that returns a return record without a tenant predicate. This is the layer that makes cross-brand data leaks structurally impossible rather than merely unlikely.

2. Scoped API tokens per sub-account

Each sub-account gets its own API token, and that token carries the tenant identity. It is not a shared partner key with a tenant id passed as a parameter, because a parameter can be forged or fat-fingered. The token is the scope. A request authenticated as brand A simply cannot address brand B rows, because the scoping in layer one reads identity from the token, not from the request body.

Authorization: Bearer sk_live_brandA_9f2c...
# The token resolves to tenant=brandA. Any tid in the
# request body or query string is ignored for scoping.

3. Per-tenant webhook endpoints and secrets

Each tenant registers their own webhook endpoint URL and gets their own signing secret. Events for brand A are delivered only to brand A endpoint, signed with brand A secret. Brand B secret cannot verify brand A payload, so even a misrouted delivery fails verification instead of being silently trusted. One shared endpoint for all tenants is the classic anti-pattern: it forces the receiver to trust a tenant id in the body, and a bug in your fan-out then delivers the wrong brand events to the wrong subscriber.

4. Signed payloads that carry the tenant id

Every webhook body is signed (HMAC over the raw bytes) and includes the tenant id explicitly. The receiver verifies the signature against their own secret and then asserts the tenant id in the payload matches the account they expect. Two checks, both cheap, and together they close the door on spoofed or cross-delivered events.

{
  "event": "return.graded",
  "tenant_id": "brandA",
  "id": "evt_01J9X7...",
  "created_at": "2026-07-04T10:22:14Z",
  "data": {
    "return_id": "rtn_5521",
    "grade": "B",
    "confidence": 0.94,
    "tags": ["worn", "missing-tag"],
    "evidence_url": "https://app.areturnz.com/e/rtn_5521"
  }
}

The grade and confidence here come from the same evidence bundle you see in the dashboard; how those grades map to actions is covered in turning grades into disposition decisions, and the A/B/C/R grading itself in proof on every return.

5. Billing rollup with per-brand usage breakdown

Isolation is not only about hiding data. It is also about attributing it. Usage breaks down per brand while billing rolls up to the partner. The partner sees one invoice; each brand line is metered separately from the same scoped event stream. If your usage counters are not tenant-scoped, your billing is wrong even when your data is safe.

Isolation layer reference

Isolation layerMechanismWhat it prevents
Query-level scopingTenant id from token injected as mandatory filter on every read/writeBrand A reading or writing brand B rows
Scoped API tokensOne token per sub-account; token is the scopeForged or mistaken tenant parameters in requests
Per-tenant webhook endpointsSeparate URL + secret per tenantCross-tenant event delivery to a shared sink
Signed payloads with tenant idHMAC over raw body + explicit tenant_id assertionSpoofed, replayed, or misrouted events being trusted
Billing rollup, per-brand usageTenant-scoped metering under a partner invoiceMisattributed usage and incorrect invoices

Failure modes when isolation is done wrong

  • Shared endpoint, trusted body. All tenants POST to one URL and you trust the tenant_id field. A fan-out bug now leaks brand A events to brand B, and neither side can tell.
  • Global API key. A single partner key with tenant passed as a parameter. One typo in a query string and a brand pulls another brand returns.
  • Unsigned or weakly signed webhooks. Without HMAC over the raw bytes, anyone who learns the endpoint can inject fake return.restocked events and corrupt inventory downstream.
  • No idempotency. Retries create duplicate records. Every event carries a stable id; the receiver must dedupe on it.
  • Untenanted metering. Data is isolated but usage counters are global, so the partner cannot bill accurately per brand.

Verifying and replaying events

A correct integration verifies before it trusts and can recover without gaps.

  1. Verify the signature against your tenant secret over the exact raw request body. Reject on mismatch.
  2. Assert the tenant id in the payload equals the account you registered the endpoint for.
  3. Dedupe on the event id so retries and replays are idempotent.
  4. Reconcile via the API. Every webhook mirrors state you can also fetch with your scoped token, so if an endpoint was down you can replay the event log and pull the current return state to close any gap. The signed-JSON API returns the same evidence bundle the event referenced.

Because tokens are scoped, replay is safe by construction: a replayed fetch for brand A can only ever return brand A data. Partner-CNAME evidence, where the evidence bundle is served under the partner own domain, is on the roadmap and layers on top of this same isolation model. For the full event and field contract, see the node spec, and for how partners wire brands into one account, the partners use case.

Frequently asked questions

Should each tenant get its own webhook secret?

Yes. A per-tenant secret means a payload signed for brand A cannot be verified by brand B, so a misrouted delivery fails safely instead of being trusted. A single shared secret defeats the purpose of signing.

How do I make webhook handling idempotent?

Dedupe on the stable event id in every payload. Store processed ids and ignore repeats. Because delivery retries and manual replays reuse the same id, idempotent handlers make both harmless.

What happens if my endpoint is down when an event fires?

Replay the event log once you are back, or reconcile directly against the scoped API, which returns the current return state and the same evidence bundle the event pointed to. Isolation guarantees a replay for one tenant can only surface that tenant data.

Can a sub-account ever see another sub-account returns?

No. Query-level tenant scoping injects the caller tenant id as a mandatory filter on every read and write, and the token carries that identity, so there is no code path that returns another account rows.

How does billing work across many brands under one partner?

Usage is metered per brand from the tenant-scoped event stream, then rolled up into a single partner invoice. The partner sees one bill; each brand line is attributed separately. See pricing for how metering is structured.

Related reading: White-Label Returns Platform: The Partner Playbook for Reselling Returns Processing

Related reading: Pricing a resold returns service: margin math for partners

#partners#multi-tenant#api
Voir en action

Une preuve sur chaque retour

Des photos, un grade d'état par IA et une chaîne de traçabilité complète, rattachés à chaque colis et accessibles via l'API.