Five ad platforms, five different envelopes, five different auth schemes — and one Worker that has to fire all of them in parallel without letting a single failure take down the rest. Here is the orchestration shell, end to end.
This is the edge-runtime fanout layer of the server-side conversion attribution stack — the shell that receives the canonical event from your CRM, verifies it, transforms it per platform, and fires all five in parallel. The per-platform envelopes live in the sibling spokes (Google Ads, Meta); this is the wrangler config, the secrets discipline, the Logpush observability, and the roughly 150-line Worker that holds it together. It ships in week 2 and runs free-tier for most service-business volumes.
wrangler.toml
The config declares identity, runtime, routes, and bindings:
name = "events-fanout"
main = "src/index.ts"
compatibility_date = "2026-05-15"
compatibility_flags = ["nodejs_compat"]
workers_dev = false
[[routes]]
pattern = "events.example.com/v1/*"
custom_domain = true
[vars]
# Non-secret IDs — recoverable from each platform's UI, fine to commit
META_PIXEL_ID = "1234567890123456"
GA4_MEASUREMENT_ID = "G-XXXXXXXXXX"
GOOGLE_ADS_CUSTOMER_ID = "1234567890"
[observability]
enabled = true
[[analytics_engine_datasets]]
binding = "FANOUT_METRICS"
dataset = "events_fanout_metrics"
Four choices earn naming: compatibility_date pins the runtime to a tested release (don't auto-track latest); the /v1/* route is versioned at the path so a v2 can ship in parallel without breaking the CRM webhook URL; [vars] holds non-secret IDs (pixel/customer IDs are recoverable from each platform's UI — committing them is fine; the actual secrets are separate); and [[analytics_engine_datasets]] declares the dataset the Worker writes per-platform success and latency to.
Secrets — wrangler secret put
The five platform credentials plus the CRM webhook signing secret go in via wrangler secret put, never the repo:
wrangler secret put GOOGLE_ADS_REFRESH_TOKEN
wrangler secret put GOOGLE_ADS_DEVELOPER_TOKEN
wrangler secret put META_CAPI_ACCESS_TOKEN
wrangler secret put MS_ADS_REFRESH_TOKEN
wrangler secret put TIKTOK_EVENTS_ACCESS_TOKEN
wrangler secret put GA4_API_SECRET
wrangler secret put CRM_WEBHOOK_SECRET # verifies the inbound webhook signature
Three rules: scope by environment (production secrets are separate from staging; CI can deploy but never read them — only the runtime can); rotate annually at minimum (platform tokens don't expire predictably, and rotation surfaces drift before it becomes a production failure); and never log secrets (a 401 from Meta logs META_CAPI_AUTH_FAILED, not the request with the token).
The Worker entry point — the fanout pattern
About 150 lines covering five jobs: verify the webhook signature, parse the canonical event, dispatch to five platforms in parallel, aggregate the results, emit metrics.
import { Hono } from 'hono'
import { verifyWebhookSignature } from './lib/crypto'
import { canonicalToGoogle } from './transforms/google'
import { canonicalToMeta } from './transforms/meta'
// …microsoft, tiktok, ga4
const app = new Hono<{ Bindings: Env }>()
app.post('/v1/events', async (c) => {
const rawBody = await c.req.text()
const signature = c.req.header('x-crm-signature') ?? ''
if (!await verifyWebhookSignature(rawBody, signature, c.env.CRM_WEBHOOK_SECRET)) {
return c.json({ error: 'invalid_signature' }, 401)
}
const canonical = JSON.parse(rawBody)
const startedAt = Date.now()
// Fire all five in parallel — one failure must not cascade
const results = await Promise.allSettled([
fireGoogle(canonicalToGoogle(canonical), c.env),
fireMeta(canonicalToMeta(canonical, c.env.META_PIXEL_ID, canonical.consent_state), c.env),
fireMicrosoft(canonicalToMicrosoft(canonical), c.env),
fireTikTok(canonicalToTikTok(canonical), c.env),
fireGA4(canonicalToGA4(canonical), c.env),
])
results.forEach((r, i) => {
const platform = ['google', 'meta', 'microsoft', 'tiktok', 'ga4'][i]
c.env.FANOUT_METRICS.writeDataPoint({
blobs: [platform, r.status, canonical.event_id],
doubles: [Date.now() - startedAt],
indexes: [canonical.event_name],
})
})
return c.json({ received: canonical.event_id })
})
export default app
Three choices earn naming: Promise.allSettled, not Promise.all — one platform failing must not cascade, so each result surfaces independently; synchronous metric emission — Analytics Engine's writeDataPoint is fire-and-forget at sub-millisecond cost, one point per fanout result for the dashboards to aggregate; and the versioned route — /v1/events is the contract surface, so a /v2 can migrate in parallel.
Observability — Logpush
Logpush ships every request log to a sink of your choice (R2, Datadog, BigQuery). Two patterns matter: dual-destination — one cheap long-retention copy to R2 for archive, one real-time copy to your APM for on-call; and structured logging at the boundary — the Worker writes one JSON line per request with event_type, platform, status, latency_ms, dedupe_key, so the sink indexes the fields and you query by platform, status, or latency instead of grepping free text.
Deploy — one shot from CI
# .github/workflows/deploy.yml
on: { push: { branches: [main] } }
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with: { node-version: '24' }
- run: npm ci
- run: npm test
- uses: cloudflare/wrangler-action@v3
with:
apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }}
command: deploy
Scope the token to deploy-only (Workers Scripts: Edit, Workers Routes: Edit), never full account. The deploy takes 5–15 seconds; a green CI build means a live edge deploy worldwide inside that window. Rollback is one command: wrangler rollback.
Closing
The Worker that anchors the fanout is roughly 150 lines, ships in week 2 of the four-week build, and runs free-tier for most service-business volumes. Pinned compatibility date, secrets separated by environment, structured logging, versioned routes — that discipline survives every CRM change, every platform quirk, and every request to add a sixth platform later. None of it is novel; the novelty is refusing to deviate from the pattern. The boring stack that prints.
Get the kit, not just the theory.
We'll send the build checklist behind this post — and the next pillar when it ships. One email, no drip sequence. Unsubscribe in one click.