Skip to main content

Merchant quickstart (10 minutes)

Add OID4Pay to your existing storefront so agentic checkouts settle to your own Stripe Connect account. You will register a merchant identity, publish a signed Offer, verify a presented mandate, and trigger the charge.

Prerequisites

Step 1: install the SDK

# Node / TypeScript
npm install @oid4pay/oid4ac-merchant

# Python
pip install oid4pay-oid4ac

# Go
go get github.com/oid4pay/oid4ac-go

The three SDKs share a single conformance harness; a Node verifier accepts the byte-exact output of a Python or Go signer.

Step 2: publish the JWKS

Host your public JWK at https://<origin>/.well-known/jwks.json:

{
  "keys": [
    {
      "kty": "OKP",
      "crv": "Ed25519",
      "kid": "merchant-2026-05-14",
      "alg": "EdDSA",
      "use": "sig",
      "x": "<base64url public key bytes>"
    }
  ]
}

Step 3: register with the Discovery directory

curl -sS https://discover.oid4pay.com/v1/merchants \
  -H "content-type: application/json" \
  -d '{
    "display_name": "Alpacanica",
    "domain": "shop.alpacanica.com",
    "country": "NL",
    "currencies_accepted": ["EUR"],
    "categories": ["apparel", "home"],
    "jwks_uri": "https://shop.alpacanica.com/.well-known/jwks.json",
    "catalog_uri": "https://shop.alpacanica.com/.well-known/oid4ac-catalog",
    "oid4ac_acceptance_methods": ["card", "sepa_debit"],
    "stripe_connect_account_id": "acct_<your acct>"
  }'

The directory verifies the JWKS reachability and DNS ownership before flipping verification_status=verified. Provisional entries are not surfaced to agents.

Step 4: serve signed Offers

Each SKU has a route that emits a JSON-LD Offer body plus the RFC 9421 Signature-Input + Signature headers. The Node SDK has a helper:

import { signOffer } from "@oid4pay/oid4ac-merchant";

const body = {
  "@context": "https://schema.org",
  "@type": "Offer",
  "sku": "test-pinata",
  "name": "Test Piñata",
  "amount_minor": 1299,
  "currency": "EUR",
  "in_stock": true,
};

const signed = await signOffer(body, {
  privateJwk,
  keyid: "merchant-2026-05-14",
  targetUri: "https://shop.alpacanica.com/products/test-pinata",
  expiresIn: 300,
});
// signed = { body, headers: { "Content-Digest", "Signature-Input", "Signature" } }

Step 5: verify mandate + JWT-AT at charge time

import { verifyOffer, verifyMandate, charge } from "@oid4pay/oid4ac-merchant";

export async function POST(request) {
  const body = await request.json();

  // 1. Verify your own offer signature (defence in depth; an agent could
  //    present a tampered offer).
  const v = await verifyOffer(body.offer, body.offerHeaders, ownJwks, {
    expectedTargetUri: `https://shop.alpacanica.com/products/${body.offer.sku}`,
  });

  // 2. Verify the SD-JWT VC mandate + KB-JWT against the AS JWKS.
  const mandate = await verifyMandate(body.sdJwtVc, body.kbJwt, asJwks, {
    expectedAudience: "https://shop.alpacanica.com",
    expectedNonce: sha256(`${merchantNonce}:${v.bodyDigest}`),
  });

  // 3. Settle the charge through the AS (which proxies Stripe Connect).
  const result = await charge({
    accessToken: body.accessToken,
    dpopProof: body.dpopProof,
    offerDigest: v.bodyDigest,
    idempotencyKey: crypto.randomUUID(),
    destinationAccount: "acct_<your acct>",
  });

  return Response.json(result);
}

Step 6: handle SSF events

The AS emits SSF (Shared Signals Framework) events for revocations, mandate updates, and disputes. Subscribe with your registered receiver URL:

curl -sS https://as.oid4pay.com/ssf/subscribe \
  -H "content-type: application/json" \
  -d '{
    "delivery_method": "https://schemas.openid.net/secevent/risc/delivery-method/push",
    "delivery_endpoint": "https://shop.alpacanica.com/ssf/receive",
    "events_requested": [
      "oid4ac.mandate.revoked",
      "oid4ac.payment.disputed"
    ]
  }'

Next steps