Multi-Tenancy
How Relay isolates tenants and scopes every operation.
Multi-tenancy is built into Relay at the data model level. Every endpoint and event carries a TenantID field that determines ownership and routing.
Tenant scoping
Endpoints
Each endpoint belongs to exactly one tenant:
ep, err := r.Endpoints().Create(ctx, endpoint.Input{
TenantID: "tenant-acme",
URL: "https://acme.example.com/webhook",
EventTypes: []string{"order.*"},
})Events
Events are scoped to a tenant. When Send() resolves matching endpoints, it only considers endpoints belonging to the same tenant:
r.Send(ctx, &event.Event{
Type: "order.created",
TenantID: "tenant-acme",
Data: orderPayload,
})The scope package
The scope package provides context-level scoping for app and organization IDs. In standalone mode, scope is a no-op:
import "github.com/xraph/relay/scope"
appID, orgID := scope.Capture(ctx) // returns empty strings in standalone mode
ctx = scope.Restore(ctx, appID, orgID)When used with the Forge framework, the scope package reads app and organization IDs from the Forge context.
Scope fields on entities
| Entity | Fields |
|---|---|
EventType | ScopeAppID |
Endpoint | ScopeAppID, ScopeOrgID |
Event | ScopeAppID, ScopeOrgID |
These fields are populated automatically by Send() using scope.Capture(ctx).