pragma.vision Your technology observatory

The press · AI Agent Development · filed 2026-06-01 · updated 2026-07-10

Dual-Protocol Implementation

Dual-Protocol Implementation

#dual-protocol #ap2 #acp #agent-payments #hybrid-signatures

The problem

Agent payments split in 2025. Google shipped AP2 with sixty-plus partners — Mastercard, Adyen, PayPal, Coinbase — around a cryptographic mandate chain. Stripe and OpenAI shipped ACP, an open standard for programmatic checkout sessions between agents and merchants. AP2 answers “did the user authorize this spend?” ACP answers “process the actual payment.” A real production system needs both, because Gemini speaks AP2 and ChatGPT speaks ACP, and the assistant market is fragmenting, not consolidating.

Most teams pick one. The choice looks economic for a sprint, then it gets expensive. AP2-only means no ChatGPT customers and no Claude customers. ACP-only means no Google Assistant, no Android Pay autonomy, no Intent Mandate model where an agent can spend within pre-authorized limits without per-transaction approval. The market reach difference is roughly the difference between 50% and 95% of the AI commerce surface in 2026.

This walks through the architecture that supports both protocols in a single codebase with 69.5% shared infrastructure: the unified mandate schema, the protocol detection middleware, the hybrid signature service, and the idempotency layer that prevents duplicate charges across both rails.

Free sample

See the structure and voice before you buy.

Download the sample (PDF)

What most people get wrong

Mistake one: building two separate codebases. The naive shape is /ap2/ and /acp/ directories with their own services, their own databases, their own crypto layers. Twelve months in you have two mandate models that drifted, two rate limiters with subtly different sliding windows, two audit logs that don’t join, and a security patch that has to be applied twice with two different test suites. The protocols share more than they differ. Nonce validation, audit logging, rate limiting, and the underlying mandate lifecycle are identical operations expressed in two different envelopes. Treating them as two systems doubles maintenance for surface novelty.

The measurable answer: 69.5% of the code is shared. Crypto service, nonce service, rate limiter, audit log, and the database access layer are 100% protocol-agnostic. The unified mandate service is 70% shared with thin AP2/ACP branches inside validateProtocolData(). Only the webhook handlers (HMAC for ACP, mandate-chain verification for AP2) are fully protocol-specific. That’s 1,180 fewer lines to write, test, secure, and migrate when the next spec revision lands.

Mistake two: verifying only the classical signature. AP2 mandates carry ECDSA P-256 signatures. ACP webhooks carry HMAC-SHA256 signatures. Both are sufficient for the protocol partner’s audit. Neither is sufficient for an agent payment system that needs to survive a quantum-computing inflection. The hybrid model is to verify the classical signature (because the protocol requires it) and a co-located ML-DSA-65 lattice signature (because future-state security requires it). Both must verify before the operation proceeds. Belt and suspenders. Either alone is a single point of cryptographic failure with a fifteen-year deprecation horizon.

This article is the short version — Dual-Protocol Implementation is the full playbook.

Get the ebook — $29

A working approach

Four layers, top to bottom:

  1. Protocol-specific handlers (15.25% AP2, 15.25% ACP) — webhook signature verification, mandate chain assembly, provider-specific data shapes.
  2. Protocol detection middleware — auto-routes the incoming request to the right handler based on headers, then bearer-token shape, then payload signals.
  3. Shared abstraction layer (69.5% shared) — UnifiedMandateService, CryptoService, NonceService, RateLimiter, AuditService.
  4. Unified database — one unified_intent_mandates table with protocol_type column and protocol_metadata JSONB.

The mandate schema is the design pivot. One table, two protocols:

CREATE TABLE unified_intent_mandates (
  id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
  protocol_type TEXT NOT NULL
    CHECK (protocol_type IN ('ap2','acp','standard')),

  -- Common fields (all protocols)
  user_id UUID NOT NULL REFERENCES users(id),
  max_amount DECIMAL(12,2) NOT NULL,
  spent_amount DECIMAL(12,2) DEFAULT 0,
  status TEXT NOT NULL,
  classical_signature TEXT NOT NULL,
  quantum_signature TEXT NOT NULL,
  nonce TEXT NOT NULL UNIQUE,
  created_at TIMESTAMPTZ DEFAULT NOW(),

  -- Protocol-specific JSONB
  ap2_data JSONB,
  acp_data JSONB,

  CONSTRAINT valid_protocol_data CHECK (
    (protocol_type = 'ap2' AND ap2_data IS NOT NULL) OR
    (protocol_type = 'acp' AND acp_data IS NOT NULL) OR
    (protocol_type = 'standard')
  )
);

The same row stores an AP2 Intent Mandate or an ACP checkout session. Common columns drive shared logic. The JSONB column carries protocol-specific fields — AP2 mandate chain ID, Google Pay token, ECDSA public key for AP2; checkout session ID, Stripe customer ID, HMAC secret reference for ACP. A cross-protocol revenue query joins the same table and groups by protocol_type.

Protocol detection uses a priority chain — explicit header wins, then auth shape, then content signals:

function detectProtocol(request: Request): ProtocolType {
  // Priority 1: Explicit protocol header
  const explicit = request.headers.get('X-Protocol-Type');
  if (explicit) return explicit as ProtocolType;

  // Priority 2: AP2 mandate signature header
  if (request.headers.get('X-Mandate-Signature')) return 'ap2';

  // Priority 3: ACP bearer token shape
  const auth = request.headers.get('Authorization');
  if (auth?.startsWith('Bearer ')) {
    // ACP uses session-scoped bearer tokens
    return 'acp';
  }

  // Priority 4: Stripe webhook signature header
  if (request.headers.get('Stripe-Signature')) return 'acp';

  return 'standard';
}

The unified mandate service consumes that protocol type and dispatches the protocol-specific validators while running every shared step exactly once:

export class UnifiedMandateService implements UnifiedMandateInterface {
  constructor(
    private db: Database,
    private crypto: CryptoService,
    private nonce: NonceService,
    private audit: AuditService
  ) {}

  async create(data: MandateCreate): Promise<Mandate> {
    // Shared: nonce validation
    await this.nonce.validateAndConsume(data.nonce);

    // Protocol-specific: validate ap2_data or acp_data
    await this.validateProtocolData(data);

    // Shared: hybrid signature verification (both must pass)
    await this.crypto.verifyHybrid(data);

    // Shared: persist
    const mandate = await this.db.insert(data);

    // Shared: audit log
    await this.audit.log({
      action: `${data.type}_mandate_created`,
      protocol_type: data.protocol_type,
      user_id: data.user_id,
      mandate_id: mandate.id
    });

    return mandate;
  }
}

Hybrid signature verification runs both checks and treats either failure as fatal:

async verifyHybrid(mandate: Mandate): Promise<void> {
  const payload = canonicalSerialize(mandate);

  const classicalOk = mandate.protocol_type === 'ap2'
    ? await verifyECDSA(payload, mandate.classical_signature, mandate.user_pubkey)
    : await verifyHMAC(payload, mandate.classical_signature, this.secrets.stripeWebhook);

  const quantumOk = await verifyMLDSA65(
    payload,
    mandate.quantum_signature,
    mandate.user_quantum_pubkey
  );

  if (!classicalOk || !quantumOk) {
    throw new SignatureError({
      classical: classicalOk,
      quantum: quantumOk,
      mandate_id: mandate.id
    });
  }
}

Idempotency caps the surface. Every payment write is wrapped in a 24-hour KV-backed idempotency cache keyed on Idempotency-Key + user_id + amount + mandate_id. A second request with the same key returns the original response. Retries don’t double-charge.

This article is the short version — Dual-Protocol Implementation is the full playbook.

Get the ebook — $29

Where this scales

The walkthrough above is the architecture spine. The full book covers six more dimensions:

  • AP2 Cart and Payment Mandate chain — how the cryptographic chain is assembled, why each mandate references the previous, and the verification flow that rejects orphaned mandates.
  • ACP Checkout Session lifecycle — the four ACP endpoints, SharedPaymentToken delegation, the bearer-token authentication model, and Stripe webhook HMAC verification with constant-time comparison.
  • RLS policies — Supabase row-level security templates that prevent IDOR across mandates, with the policy structure that survives the 100% RLS coverage audit.
  • Cross-protocol queries — analytic SQL that unions AP2 and ACP revenue, groups by user, and exposes payment-method preference without duplicating reporting logic per protocol.
  • E2E test architecture — 14 AP2 tests, 9 ACP tests, 283 shared-layer unit tests, and the cross-protocol test that exercises both rails through the same user session.
  • Adding a third protocol — the directory structure and interface contract that lets a new protocol (Visa TAP identity, eIDAS-2 ledger, future agent payment specs) land as a validateProtocolData() branch plus a webhook handler instead of a parallel system.

Every code example in the book has been run against production AP2 mandates from the Google reference stack and production ACP sessions from a live Stripe Checkout endpoint. The 69.5% sharing number is a measured line count from the soft.house API, not an estimate.

Included with the book

  • protocol-detection-middleware.ts — a working middleware implementation with header-based detection, intelligent AI-assistant fallback, and confidence scoring. MIT-licensed; drop into a Cloudflare Worker or any Edge runtime.

Get the full picture

The full playbook

Dual-Protocol Implementation — everything this article compresses, worked through end to end.

Get the ebook — $29

Readers of this also chose

Questions readers ask

Do I need to ship both protocols on day one?

No. The book ships an architecture that supports both, but the order most teams pick is AP2 first (Sprints 1–2), then ACP (Sprint 3), then dual-protocol revenue (Sprint 4+). The shared layer is built once. The second protocol adds the webhook handler and the JSONB metadata branch and inherits everything else.

Does the hybrid signature model require a quantum computer to use?

No. ML-DSA-65 is a classical-computer algorithm; it runs on standard CPUs. The point of the hybrid model is that the classical signature satisfies the protocol partner's spec (Google for AP2, Stripe for ACP) and the lattice signature future-proofs the same operation against quantum attack. Both are computed and verified on commodity hardware.

What database does this assume?

PostgreSQL with JSONB support. The reference implementation runs on Supabase, with RLS policies for tenant isolation. The schema works on any PostgreSQL 14+ instance — the JSONB column constraints, the CHECK constraint on protocol_type, and the unique nonce index are all portable.

How does this interact with the Visa TAP identity layer?

TAP sits above AP2 and ACP — it answers "is this agent legitimate?" The dual-protocol architecture in this book covers the authorization and execution layers. The book's last chapter ("Future-Proofing") covers how a TAP identity check lands as a pre-mandate middleware step without changing the unified service contract.

What's the refund policy?

Lemon Squeezy's standard refund window applies. If the patterns don't work for your stack, the refund link is in the receipt email.

Your opinion

Tell us anything.

What works, what doesn't, what's missing — especially about our watches, lenses, and the register itself. Anonymous is fine; leave an email if you'd like a reply.