Curious how you’re handling multi-tenant event fan-out without standing up Kafka everywhere; I’m testing EventBridge API Destinations into GCP Pub/Sub with a tenant-id header and seeing about 11k msgs/sec at p95 < 80 ms, but I want simpler schema governance that doesn’t slow integrations. If you’ve kept routing portable across clouds and SaaS webhooks, what made it scalable without turning into glue code?
I’ve had good luck keeping this portable by standardizing on CloudEvents 1.0 and stamping a ‘contract-id’ next to your ‘tenant-id’, then validating per-contract JSON Schema at the first subscriber so EventBridge → Pub/Sub can fan out cleanly without central gating (https://cloudevents.io). We held about 12k msgs/sec with p95 about 70 ms, but the caveat is you need a tiny edge validator (Cloud Run on push or a small Lambda) or bad payloads will leak through.
But we ended up putting a tiny edge router (Cloud Run/Workers) in front of EventBridge and Pub/Sub that keeps a tenant→destinations map in KV, stamps an idempotency key, and fans out via HTTP so SaaS webhooks and GCP topics look the same; the trick was ‘envelope strict, payload permissive’ and sampling about 1% for schema validation to keep p95 low. Small caveat: with API Destinations, give each tenant a retry budget and DLQ so a noisy neighbor can’t stall the rest.
Kept it scalable by embedding a semver “schema” and “event-type” in the envelope next to your “tenant-id header,” and only doing a cheap existence check at ingress; full JSON Schema validation runs async, with bad events shunted to a per-tenant DLQ so fan-out stays hot. We publish schemas to a versioned bucket and ETag them so consumers across clouds fetch the exact doc the event referenced — no central registry bottleneck, and we’ve held about 11k/s at p95 < 80 ms. Tiny caveat: cache schema docs for minutes but invalidate on new ETags or you’ll ship stale rules under load.
We stopped arguing about central registries and just stamp a ‘schema-hash’ (sha256 of the normalized payload) alongside your per-tenant header at ingress; producers drop the canonical schema JSON in S3/GCS keyed by that hash, and consumers fetch/cache on first see. At about 11k/sec we saw single-digit ms overhead and p95 stayed under your 80 ms; governance lives in Git via AsyncAPI (https://www.asyncapi.com/), but runtime is just hashing, not validation (, registry sprawl). Small caveat: you need a consistent canonicalizer or identical payloads won’t hash the same — we used jq --sort-keys.