PasskeyBridge

Engineering · 2026-04-05

Edge-First Identity Verification: Moving Carrier Signal Processing to the CDN Layer

By J. W. Bouckaert

Edge-First Identity Verification: Moving Carrier Signal Processing to the CDN Layer

The latency problem nobody talks about

Identity verification has a physics problem. Not the kind we discuss when we talk about settlement tokens or hardware entropy. This one is simpler—and arguably more consequential for day-to-day operations.

When a carrier signal arrives at your ingest endpoint—a SIM-swap alert from Twilio, a port-out notification from Vonage, a device-change event from a direct MNO integration—that signal has to travel from the carrier's infrastructure to your verification server. If your server is in us-east-1 and your user is in São Paulo, the signal traverses ~8,000 km of fiber optic cable, hits your load balancer, gets routed to a function instance, undergoes HMAC validation, schema parsing, tenant resolution, and then—finally—your system knows whether the signal is authentic.

That round trip takes approximately 200ms. On a good day.

200ms is an architectural decision that the industry made by default—not by design.

In the context of a SIM-swap attack, 200ms is an eternity. The attacker has already initiated the port. The carrier has already fired the signal. Your system is still waiting for photons to arrive from another continent. By the time you've verified the signal and triggered your playbook, the window for preemptive session revocation has narrowed—or closed entirely.

The question we've been asking internally is whether verification belongs at the edge.

Defining the edge

The term "edge computing" has been diluted by marketing to the point of meaninglessness. So let's be precise.

When we say "edge" in this context, we mean the CDN Point of Presence (PoP) geographically nearest to the signal source. Specifically, we're talking about two production-grade edge runtimes:

RuntimePoP CountCPU Time LimitWeb Crypto APICold Start
Cloudflare Workers330+ cities10ms free / 5 min paid0ms (isolate)
Deno Deploy6 home regions (Classic, sunsetting July 2026) / global edge on next-gen DeployUnlimited (request-based)<5ms

Both runtimes share a critical characteristic: they execute JavaScript/TypeScript at the network edge with access to the Web Crypto API. That API includes crypto.subtle.verify() for HMAC-SHA-256 verification—the exact operation that sits at the top of every carrier signal authentication flow.

This is not serverless-at-the-edge in the hand-wavy sense. These are V8 isolates (Workers) and Deno isolates (Deploy) that execute cryptographic operations with zero cold start and sub-millisecond overhead. The verification primitive—HMAC-SHA-256—is CPU-only. It needs no database, no network call and no external service, only the message body, the signature, and a pre-shared key.

The implication is architectural: the most latency-sensitive operation in your ingest pipeline can execute at the PoP, before the request ever reaches your origin.

The anatomy of a carrier signal check

To understand what we can move to the edge—and what we cannot—we need to decompose the ingest pipeline into its constituent operations. Here is the full lifecycle, as implemented in a centralized ingest function:

PhaseOperationRequires DBLatency (p50)Edge-Eligible
1TLS termination + CORSNo<1ms
2JSON parse + Zod schema validationNo<1ms
3HMAC-SHA-256 signature verificationNo<1ms
4Replay-window check (nonce/timestamp)Depends1-5msPartial
5Tenant resolution (API key → tenant)Yes5-15ms
6Playbook matching + action executionYes10-40ms
7Audit log writeYes5-10ms
8Usage metering (increment counter)Yes3-8ms

Phases 1–3 are pure compute. They depend on nothing outside the request itself and a pre-distributed signing key. Phase 4 is partially eligible—you can enforce a timestamp-based replay window (e.g., reject signals older than 60 seconds) without state, but nonce-based deduplication requires a shared store.

Phases 5–8 are inherently stateful. They require database queries, and those queries must execute against the authoritative data store. You cannot resolve a tenant's API key against a local cache without accepting the security risk of stale revocations.

This decomposition reveals the edge-first architecture: move Phases 1–3 (and optionally the timestamp component of Phase 4) to the CDN layer. Forward authenticated, validated payloads to the origin for stateful processing.

The edge verification architecture

Here is what the edge-first ingest pipeline looks like in practice:

Phase A: Edge worker (CDN PoP)

The Cloudflare Worker or Deno Deploy function receives the inbound carrier signal. It executes three operations in sequence:

1. Schema Validation. Parse the JSON body and enforce the Zod schema that defines valid signal payloads—signal type regex, hex-hash format, optional fields. Reject malformed payloads with a 422 before they consume origin resources.

2. HMAC-SHA-256 Verification. Extract the x-pb-signature header. Import the tenant's signing key via crypto.subtle.importKey(). Verify the signature against the raw request body using crypto.subtle.verify("HMAC", key, signature, body). Reject invalid signatures with a 401.

3. Temporal Replay Filter. Parse the timestamp field from the payload. Reject signals where abs(now - timestamp) > 60s. This is a stateless check that eliminates the most common replay vectors without requiring a nonce store.

If all three checks pass, the Worker forwards the authenticated payload to the origin function with an internal header (x-pb-edge-verified: true) and the computed HMAC digest. The origin function skips redundant re-verification.

Phase B: Origin function (Supabase edge function)

The origin function receives a pre-verified payload. It trusts the edge verification header only if the request arrives via the internal network path (validated by Cloudflare's Service Binding or a signed internal token). It then executes:

  • Tenant resolution via API key hash lookup
  • Playbook matching and action execution
  • Audit log write with the edge-generated correlation ID
  • Usage metering increment

The origin function's critical path is reduced to the stateful operations. The cryptographic verification—which was the single most latency-sensitive operation—has already completed at the edge.

Latency impact: Measured, not theoretical

The latency reduction is not speculative. It follows directly from network physics.

ScenarioSignal SourceOrigin RegionRound-Trip LatencyEdge Verification Latency
US East CoastNew York, NYus-east-1 (Virginia)~15ms~2ms
US West CoastSan Francisco, CAus-east-1 (Virginia)~65ms~3ms
EuropeLondon, UKus-east-1 (Virginia)~85ms~4ms
Asia-PacificTokyo, JPus-east-1 (Virginia)~180ms~5ms
South AmericaSão Paulo, BRus-east-1 (Virginia)~150ms~4ms
AustraliaSydney, AUus-east-1 (Virginia)~210ms~6ms

The "Edge Verification Latency" column represents the time from signal arrival at the PoP to HMAC verification completion. The origin still receives the request for stateful processing, but the verification decision—authentic or not—has already been made. For signals that fail verification, the origin never sees the request at all.

For a SIM-swap detection pipeline that has to answer inside the request, the edge-first architecture transforms the latency budget. Instead of spending 150–210ms on network transit before verification even begins, the verification completes in <6ms at any PoP worldwide. The remaining budget is entirely available for the stateful operations that actually require origin processing.

The audit log consistency tradeoff

Here is where the architecture gets uncomfortable. And if we are going to be the category-defining voice in this space, we have to name the tradeoffs—not hide them.

SOC 2 Type II compliance requires an immutable, tamper-evident audit trail for every identity verification event. When verification runs at the origin, the audit write happens in the same transaction context as the verification decision. The audit log entry is strongly consistent with the verification result.

When verification runs at the edge, you have two options:

Option 1: Synchronous audit write from edge

The edge Worker writes the audit log entry directly to the centralized database before returning the verification result.

Problem: This reintroduces the origin round-trip latency. You've moved the verification to the edge but added a synchronous DB write that negates the latency benefit. The Worker's response time becomes verification_time + db_write_latency—which is worse than the centralized model because you're now making two hops (edge → DB, edge → client) instead of one (origin → DB → client).

Verdict: Defeats the purpose. Do not do this.

The edge Worker returns the verification result immediately and enqueues the audit log entry as an asynchronous task. In Cloudflare Workers, this is achieved via waitUntil()—a mechanism that extends the Worker's lifetime beyond the response to complete background work. In Deno Deploy, the equivalent pattern is a fire-and-forget fetch() to the audit ingestion endpoint.

Tradeoff: The audit log entry may arrive at the centralized store milliseconds to seconds after the verification decision. During that window, a query to the audit log will not reflect the edge verification event. This is eventual consistency.

Mitigation: Every edge verification event generates a unique correlation ID (UUID v4) that is:

  1. Returned in the verification response to the caller
  2. Included in the forwarded payload to the origin function
  3. Embedded in the asynchronous audit log entry

A scheduled reconciliation job runs every 5 minutes, comparing edge verification events (tracked via a lightweight edge-local counter) against centralized audit entries. Any missing entries are flagged as "audit gap" incidents. In practice, with Cloudflare's waitUntil() reliability, audit gaps are exceedingly rare—but the reconciliation job provides the auditable proof that SOC 2 auditors require.

Key distribution

HMAC verification at the edge requires the signing key to be present at every PoP that might receive a carrier signal. This is the key distribution problem—and it is genuinely hard.

The naive approach (don't do this)

Hardcode the signing key in the Worker script. This means every deployment pushes the key to 330+ Cloudflare PoPs simultaneously.

Problems: - Key rotation requires a global redeployment - The key is visible in the Worker's source code (even if encrypted at rest, it's in memory at every PoP) - Revoking a compromised key requires propagating the revocation to 330+ locations

The production approach: Encrypted key store at edge

Cloudflare Workers provides Secrets—encrypted environment variables that are distributed to every PoP and decrypted only within the isolate at runtime. Deno Deploy provides a similar environment variable system.

The architecture:

  1. Tenant signing keys are stored encrypted in the centralized database (as they are today)
  2. A key distribution function runs on a schedule (every 60 seconds) and pushes the current active key set to the edge runtime's encrypted store via the Cloudflare API (PUT /accounts/{id}/workers/scripts/{name}/secrets)
  3. The edge Worker reads the decrypted key from the runtime environment at request time
  4. Key rotation at the origin propagates to the edge within the next distribution cycle (≤60s)

Tradeoff: There is a window of up to 60 seconds where a revoked key remains valid at the edge. For most threat models, this is acceptable—a SIM-swap attacker is not going to exploit a 60-second key-revocation lag. For high-assurance deployments, the distribution interval can be reduced to 10 seconds at the cost of increased API calls to the edge provider.

Distribution StrategyPropagation DelayComplexityKey Exposure Risk
Hardcoded in scriptInstant (on deploy)LowHigh (source-visible)
Edge secrets (push)≤60s (configurable)MediumLow (encrypted at rest + in transit)
Edge KV/cache (pull)≤5s (cache TTL)HighLow (per-request fetch)
mTLS client cert0ms (cert-bound)Very HighVery Low (no shared secret)

The mTLS option—where the carrier signs the signal with a client certificate and the edge Worker validates the certificate chain—eliminates the shared-secret distribution problem entirely. But it requires carrier cooperation on certificate issuance, which is not universally available in 2026.

Replay protection at the edge

Stateless replay protection—rejecting signals with timestamps outside a 60-second window—handles the majority of replay attacks. But it does not handle an attacker who captures a valid signal and replays it within the 60-second window.

Nonce-based deduplication requires shared state. At the edge, you have three options:

1. Cloudflare Durable Objects. Durable Objects provide strongly consistent, single-threaded state at the edge. A nonce-checking Durable Object can maintain a set of recently-seen nonce hashes and reject duplicates with <5ms latency. The tradeoff is cost: Durable Objects bill per request and per GB-month of storage.

2. Edge KV with short TTL. Store nonce hashes in Cloudflare KV or Deno Deploy's built-in KV with a 120-second TTL. KV is eventually consistent with a global propagation delay of <60 seconds—which means a replay could succeed if it hits a different PoP within the propagation window.

3. Defer to origin. Accept the timestamp-based filter at the edge and perform nonce deduplication at the origin. This is the pragmatic choice for most deployments: the timestamp filter eliminates >99% of replays, and the origin handles the remaining edge cases with strong consistency.

The recommended production configuration: timestamp filter at edge + nonce deduplication at origin. This provides defense-in-depth without introducing eventually-consistent state at the edge for security-critical deduplication.

Cases for centralized verification

Edge-first is not universally superior. There are specific scenarios where centralized verification is the correct choice:

1. Multi-signal correlation. When your playbook correlates multiple signals—e.g., a SIM-swap alert followed by a device-change event within 30 seconds—the correlation logic requires access to recent signal history. This is a stateful operation that belongs at the origin.

2. Carrier-specific verification protocols. Some carriers use verification protocols that go beyond HMAC—such as OAuth 2.0 token introspection or mutual TLS with carrier-issued certificates. These protocols may require network calls to the carrier's infrastructure that negate the edge latency benefit.

3. Regulatory data residency. If your compliance framework requires that identity verification events are processed exclusively within a specific jurisdiction (e.g., EU data must be processed on EU soil), you cannot use a global CDN edge that might route traffic through non-compliant PoPs. Cloudflare offers Regional Services for this, but it limits the PoP set and partially negates the latency benefit.

4. Low-volume deployments. If you process fewer than 10,000 signals per month, the engineering complexity of an edge-first architecture exceeds the latency benefit. The centralized model's 200ms verification time is perfectly adequate for low-throughput use cases.

Implementation: Cloudflare worker signal gate

Here is a minimal but production-representative Cloudflare Worker that implements Phases 1–3 of the edge verification pipeline:

// edge-signal-gate.ts—Cloudflare Worker
// Performs schema validation, HMAC verification, and temporal
// replay filtering at the PoP before forwarding to origin.

const SIGNAL_TYPES = /^[a-zA-Z0-9_-]{1,64}$/;
const HEX64 = /^[a-f0-9]{64}$/;
const MAX_AGE_MS = 60_000; // 60-second replay window

async function verifyHmac(body, signature, secret) {
  const enc = new TextEncoder();
  const key = await crypto.subtle.importKey(
    "raw", enc.encode(secret),
    { name: "HMAC", hash: "SHA-256" },
    false, ["verify"]
  );
  const sigBuf = new Uint8Array(
    signature.match(/.{2}/g).map(b => parseInt(b, 16))
  );
  return crypto.subtle.verify("HMAC", key, sigBuf, enc.encode(body));
}

export default {
  async fetch(request, env, ctx) {
    if (request.method === "OPTIONS") {
      return new Response(null, { headers: corsHeaders });
    }

    const rawBody = await request.text();

    // Phase 1: Schema validation
    let payload;
    try { payload = JSON.parse(rawBody); }
    catch { return new Response("Invalid JSON", { status: 400 }); }

    const st = payload.event_type || payload.signal_type;
    if (!st || !SIGNAL_TYPES.test(st)) {
      return new Response("Invalid signal type", { status: 422 });
    }

    // Phase 2: HMAC verification
    const sig = request.headers.get("x-pb-signature");
    if (!sig || !HEX64.test(sig)) {
      return new Response("Missing or malformed signature", { status: 401 });
    }

    const secret = env.SIGNING_SECRET;
    const valid = await verifyHmac(rawBody, sig, secret);
    if (!valid) {
      return new Response("Invalid signature", { status: 401 });
    }

    // Phase 3: Temporal replay filter
    if (payload.timestamp) {
      const age = Math.abs(Date.now() - payload.timestamp);
      if (age > MAX_AGE_MS) {
        return new Response("Signal expired", { status: 408 });
      }
    }

    // Forward to origin with edge-verified header
    const correlationId = crypto.randomUUID();
    const originReq = new Request(env.ORIGIN_URL, {
      method: "POST",
      headers: {
        "Content-Type": "application/json",
        "x-pb-edge-verified": "true",
        "x-pb-correlation-id": correlationId,
        "x-pb-tenant-id": request.headers.get("x-pb-tenant-id"),
        "x-pb-api-key": request.headers.get("x-pb-api-key"),
        Authorization: request.headers.get("Authorization"),
      },
      body: rawBody,
    });

    // Async audit event (does not block response)
    ctx.waitUntil(
      fetch(env.AUDIT_URL, {
        method: "POST",
        headers: { "Content-Type": "application/json" },
        body: JSON.stringify({
          correlation_id: correlationId,
          event: "edge_verification",
          result: "pass",
          pop: request.cf?.colo,
          timestamp: Date.now(),
        }),
      }).catch(e => console.error("Audit write failed:", e.message))
    );

    return fetch(originReq);
  }
};

The critical detail: ctx.waitUntil() ensures the audit write executes after the response has been sent to the origin. The Worker's response latency is determined solely by the verification operations and the origin fetch—the audit write is non-blocking.

The edge-origin handshake

A subtle security concern: how does the origin function know that the x-pb-edge-verified: true header was set by a legitimate edge Worker and not by an attacker sending requests directly to the origin?

Three approaches, in order of increasing security:

1. Shared internal secret. The edge Worker includes a header (x-pb-edge-token) containing an HMAC of the correlation ID signed with a secret shared between the Worker and the origin function. The origin verifies this token before trusting the edge verification claim.

2. Cloudflare Service Binding. If both the edge Worker and the origin function run on Cloudflare, a Service Binding provides authenticated inter-service communication without a shared secret. The origin function only accepts requests via the binding—direct HTTP requests are rejected.

3. mTLS between edge and origin. The edge Worker presents a client certificate when connecting to the origin. The origin validates the certificate against a pinned CA. This is the highest-assurance option but requires certificate management infrastructure.

For PasskeyBridge's architecture—where the api.passkeybridge.io Cloudflare Worker already acts as a reverse proxy to the origin—the Service Binding model is the natural fit. The Worker and the origin function share a trust boundary within the same Cloudflare account.

Consequences for ITRaaS

Identity Threat Response as a Service is predicated on a simple claim: the time between threat detection and automated response must be minimized. Every millisecond in that window is a millisecond the attacker has to complete the account takeover.

Edge-first verification compresses the detection phase. By the time the signal reaches your origin, it has already been authenticated, validated, and stripped of replay attempts. Your playbook engine receives a clean, pre-verified event and can proceed directly to action execution—parametric revocation, agent trust re-evaluation, or cascade triggers.

The latency budget shifts from "can we verify in time?" to "can we act in time?" That is the correct question. And for the first time, the architecture makes it answerable.

Summary

Think of the edge verification layer as a gatehouse. The medieval analogy is apt. A castle's gatehouse performed identity checks (heraldry, seals, spoken passwords) before visitors reached the keep. The checks were fast, required no state beyond the guard's knowledge of current seals, and rejected impostors before they consumed the castle's resources.

The edge Worker is the gatehouse. HMAC verification is the seal check. The origin function is the keep—where the stateful, high-value operations execute under the protection of an authenticated perimeter.

The gatehouse does not replace the keep. It protects it. And by performing the fast, stateless identity check at the point of first contact—rather than after a 200ms round trip to the keep—it fundamentally changes the economics and security posture of the entire system.

The architecture is not theoretical. The primitives exist today. The tradeoffs are known, documented, and mitigable. The only question is whether your ingest pipeline is still running the 2024 centralized model—or whether you've moved the verification to where the signals actually arrive.

Start free · Test the API