PINT. A person opens it in a browser and is bounced to the canonical payer-facing resource. A machine hits the same URL with Accept: application/json and receives a 402 Payment Required challenge over x402. Both paths converge on the same signature. One URL, two audiences, one PINT underneath.
This guide is for partners in the x402 private alpha. If you have not yet read the Payment Links and x402 concept page, start there — it frames the mental model this quickstart assumes.
01 / PREREQUISITES
Before you start.
Three things have to be in place before the first call lands. Each is a one-time setup step for the owner account.- A Platform API credential. Every owner-facing route (
POST /v0/payment-links, list, detail) requires a user-status gate — the authenticated account must be onboarded to at leastpending. See authentication for the JWT flow. - A signer wallet. The
PurchaseIntentcarried by the link is an EIP-712 payload signed by the owner’s wallet. The signature is the cryptographic anchor — the link’s slug is literally the signature bytes, base64url-encoded. See PINTs for the mint flow, scope catalogue, and the canonical EIP-712 domain. - A payer-side wallet that speaks x402. Any EIP-3009-capable wallet on a supported chain can settle the link. Browser payers land on the hosted pay view; agents negotiate the 402 handshake directly.
02 / CREATE A LINK
Sign once, share forever.
A payment link is created with a single POST. The owner signs the innerPurchaseIntent with their wallet, submits that payload alongside the hex ECDSA signature, and the API returns a PaymentLinkResponse whose slug is deterministically derived from the signature bytes. Re-signing the same intent produces the same slug — this is intentional and guards against duplicate issuance.
1
Sign the PurchaseIntent payload
The inner payload is an EIP-712
PurchaseIntent struct. See PINTs for the domain separator, type definitions, and a signing walkthrough. The output is a 130-hex-character ECDSA signature (0x + r + s + v).2
POST to /v0/payment-links
Submit the payload, signature, accepted chains, and an expiry. The API verifies the signature against the payload, mints the underlying PINT, derives the slug, persists the link, and returns the full response with navigation.
3
Share the slug or the pay URL
The owner keeps two artifacts: the
slug (the canonical ID) and _links.pay.href (the public URL to share with a payer).Request
SeePOST /v0/payment-links for the full parameter and response schema reference.
Request fields
Response — 201 Created
Key response fields
The slug is the signature. Two links cannot exist for the same intent.
03 / SHARE AND INSPECT
Three surfaces, one truth.
A payment link speaks through three surfaces: the shareable pay URL, the public payer view, and the owner-scoped inspection endpoints. They are three windows onto the same persisted object — nothing about the link changes based on which surface reads it.The shareable pay URL — /pay/{slug}
See GET /pay/{slug}.
This is the URL you hand to a payer. It is protocol-agnostic: a browser gets an HTML page that meta-refreshes to the public view; an agent sets Accept: application/json and begins the x402 handshake. See section 04 for the agent path.
The public payer view — GET /v0/payment-links/public/{slug}
See GET /v0/payment-links/public/{slug}.
A payer-facing JSON projection safe to render without authentication. It omits what the owner should not share publicly and includes the full signed EIP-712 mandate so a wallet or agent can verify the signature locally before settling.
410 Gone with PAYMENT_LINK_EXPIRED once the effective expiry has passed.
Owner inspection — authenticated
The owner can fetch a single link or list their links with filters. Both endpoints require the same JWT used for creation. Get one link:GET /v0/payment-links/{slug}.
Returns the same PaymentLinkResponse shape as POST. If the slug does not exist — or exists but belongs to another user — the response is 404 PAYMENT_LINK_NOT_FOUND. Ownership is not disclosed.
List with filters:
GET /v0/payment-links.
The response carries HAL pagination with all filters preserved across pages:
04 / THE x402 HANDSHAKE
Two round trips. One signature.
An agent that resolves/pay/{slug} sets Accept: application/json and receives a 402 Payment Required response carrying an x402 v2 challenge. The agent signs the challenge with its payer wallet, base64-encodes the signed envelope, and retries the same URL with the envelope in the PAYMENT-SIGNATURE header. The server validates the envelope, dispatches to the x402 facilitator, and returns a settlement receipt.
The 402 challenge shape
On the first hit (Accept: application/json, no PAYMENT-SIGNATURE), the response is 402 X402PaymentRequired:
accepts entry is returned per chain in the owner’s accepted_chains, in the order the owner provided. Only the exact scheme is supported today; an envelope carrying a different scheme is rejected with 400 PAYMENT_LINK_X402_UNSUPPORTED_SCHEME.
The retry
The agent constructs an x402 signed-payload envelope, base64-encodes it (standard base64, not base64url), and retries:x402Version, scheme, and network are checked against the link’s accepted set. The inner payload is scheme-specific (EIP-3009 fields for exact on EVM) and is carried opaquely to the facilitator.
A successful retry returns 200 X402SettlementResponse:
409 PAYMENT_LINK_ALREADY_SETTLED regardless of envelope validity — the check sits above the signature branch. The signature does not reopen a closed link.
05 / LIFECYCLE AND EXPIRY
Pending. Settled. Expired.
A payment link has three lifecycle states.settled and expired are terminal.
Effective expiry is the stricter of
payment_link.expires_at and the underlying pint.expires_at. Both are persisted as epoch milliseconds — the API normalises the PINT’s seconds-valued EIP-712 input on the server. Whichever deadline comes first closes the window for both the public view and the agent pay route.
Once a link is settled, further retries return 409 PAYMENT_LINK_ALREADY_SETTLED. The public view continues to render — settled_at, tx_hash, and payer_addr are now populated and serve as a permanent receipt. A settled link is a permanent record, not a deleted one.
06 / ERRORS
RFC 7807, everywhere.
Every error is returned as RFC 7807 Problem Details with a stableerror_code. The ones a partner will encounter most:
The
instance field on each error points to the route that emitted it; the trace_id field ties it to your Logfire span for support. One format, one code space — read the code and act.
07 / SEE ALSO
Related.
Payment Links and x402
The concept page — why the URL and the 402 carry the same signature, and what the verifier checks.
Purchase Intents (PINTs)
The signed primitive underneath every payment link. Mint flow, scopes, and EIP-712 shape.
Accept a PINT at checkout
The verifier-side flow — what a merchant does when a payer presents a PINT JWT.
Error handling
Full RFC 7807 reference and the stable error-code catalogue.
Reference
Supported chains
At private-alpha launch, Sei is the settlement network. Base, Optimism, and Tempo follow shortly after. Theaccepted_chains field stays enum-typed across the roster — code against the enum and new networks surface without an API change.
Lifecycle statuses
x402 schemes
Questions.
Can the same PurchaseIntent back two links?
Can the same PurchaseIntent back two links?
No. The slug is the signature bytes, url-safe base64-encoded — signing the same intent twice produces the same slug, and the second create call returns
409 PAYMENT_LINK_SLUG_CONFLICT rather than issuing a duplicate. This is intended: one signed intent, one link.What happens if `max_uses` is omitted?
What happens if `max_uses` is omitted?
The link accepts settlements until its effective expiry. Most integrations want
max_uses: 1 — it is the safer default for invoice-style flows.Why do owner and public views return different shapes?
Why do owner and public views return different shapes?
The owner view mirrors the create response — it is the authoritative object, including
pint_uri and usage_count useful for reconciliation. The public view adds the full EIP-712 message, signature, and nonce so a payer wallet can verify locally before settling. Neither view exposes anything that would compromise the link.Which chains will be supported first?
Which chains will be supported first?
Sei at launch. Base, Optimism, and Tempo follow shortly after. The Sei testnet (
1328) is available for integration testing. The accepted_chains field stays enum-typed across the roster — code against the enum and new networks surface without an API change.