Skip to main content

Merchant quickstart (10 minutes)

Add the OID4Pay verifier to your existing storefront or platform so it can check agent mandates before the charge. You will register a merchant identity, publish a signed Offer, and verify both the Offer signature and the presented mandate at request time. The merchant SDK is verify-only: it confirms offers, mandates, and catalogs. It never charges. The service is a developer preview and runs in test mode: no real money moves, and OID4Pay does not operate a merchant for you.

Prerequisites

Step 1: install the SDK

Not published yet. The packages are not on npm, PyPI or the Go module proxy yet, so the install commands on this page do not work today. They show the names the packages will ship under.
# 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: get listed in the Discovery directory

During the developer preview OID4Pay adds merchants to the directory itself. Talk to us with your merchant audience (your https:// origin), your catalog URL, your JWKS URL, a contact email and your country. Anyone can read the listing:

curl -sS https://discover.oid4pay.com/merchants

New entries are not listed until they pass verification: the directory fetches the merchant's JWKS and catalog and checks the catalog's RFC 9421 signature against that JWKS. On success the entry becomes verification_status=verified.

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. Your storefront signs the offer with the Ed25519 private key whose public half you published in Step 2. The merchant SDK does not sign; it verifies. Use canonicalOfferDigest to compute the same canonical SHA-256 over the body that the verifier computes, so the Content-Digest header you emit matches:

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

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

// base64url SHA-256 over the canonical JSON of the body.
const digest = canonicalOfferDigest(body);
// Emit it as the Content-Digest header value: `sha-256=:${digest}:`
// (digest is already base64url-encoded; do not re-encode it). Then sign the
// RFC 9421 signature base with your Ed25519 private key.

Step 5: verify offer + mandate at request time

When an agent presents a mandate, verify your own Offer signature and the SD-JWT VC mandate before you act. Both calls are pure verification; neither moves money. The mandate presentation is the whole compact string (SD-JWT VC, disclosures, then KB-JWT), passed as a single argument; the merchant audience is the second argument.

import { verifyOffer, verifyMandate } 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 offer = 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. The
  //    presentation is the full compact string; the audience is positional.
  const mandate = await verifyMandate(body.presentation, "https://shop.alpacanica.com", {
    jwksUrl: "https://as.oid4pay.com/.well-known/jwks.json",
    expectedIssuer: "https://as.oid4pay.com",
    expectedOfferDigest: offer.bodyDigest,
  });

  // Verification passed. mandate.amountMinor / mandate.currency / mandate.cnfJkt
  // are now trustworthy. Settlement (the charge) is performed by the
  // Authorization Server over HTTP, not by this SDK.

  return Response.json({ ok: true, mandateId: mandate.mandateId });
}

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