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
- A Stripe Connect account (the settlement rail).
- A storefront with a route per SKU (the Offer URL).
- One Ed25519 keypair for signing offers; the public JWK is published at
/.well-known/jwks.jsonon your origin.
Step 1: install the SDK
# Node / TypeScript
npm install @oid4pay/oid4ac-merchant
# Python
pip install oid4pay-oid4ac
# Go
go get github.com/oid4pay/oid4ac-goThe 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
- Read the mandate wire shape for the verifier rules.
- Wire a CDN-side drop-in instead of a server route: see cdn-bundle.
- Subscribe to the changelog for breaking wire-version bumps.