Developers

Developers

This page describes the API of the Payzilla devnet preview, so you can see how a seller and a buyer talk to it. The API runs on a private staging environment, which we switch on when we test and scale down when it is idle, and there is no public base URL yet. Merchant keys and seller wallets are set up by us. If you want to run the flow, get in touch or read how to try the preview.

Reference for the devnet preview. A private staging environment exists; there is no public base URL yet. Names and shapes may change.

How to try itSecurity and trust

Overview

There are three roles. A buyer, often an AI agent, wants a resource. A seller runs the API or tool that charges for it. Payzilla acts as the payment facilitator: it creates the payment challenge, verifies and settles the signed payment on Solana devnet, and records everything in one ledger. For the plain-English story, see how a payment works. The preview uses x402 version 2, the exact scheme, on Solana devnet, with a test token.

Authentication

  • Send your merchant key in a header named x-api-key. The value is the key itself, with no prefix.
  • Send a unique Idempotency-Key (up to 255 characters) when you create an intent. The same key with the same body returns the same intent. The same key with a different body returns 409.
  • A key belongs to one merchant. Keys are random, shown once and stored only as a hash. Every read runs as that merchant, and another merchant’s payments return 404.

Endpoints

Endpoints
Method and pathAuthWhat it does
GET /healthzNoneLiveness check. Returns ok true with 200, or ok false with 503.
GET /supportedMerchant keyThe payment kinds supported (x402 v2, scheme exact, Solana devnet) and the network-fee payer address.
POST /verifyMerchant keyChecks a signed payment against the payment requirements. Read-only. Returns isValid, an invalid reason if any, and the payer.
POST /settleMerchant keySettles a verified payment. Idempotent. It waits up to 10 seconds for the payment to be released; otherwise it returns success false with the reason settlement_pending and a transaction signature.
POST /v1/intentsMerchant key and Idempotency-KeyCreates a payment intent and returns the intent id, its status, an expiry and the payment challenge to send to the buyer.
GET /v1/paymentsMerchant keyLists your payments, newest first. Query: limit (1 to 200, default 50) and before (a cursor).
GET /v1/payments/:idMerchant keyOne payment with its attempts, timeline and ledger entries.
POST /v1/checkout/sessionsMerchant key and Idempotency-KeyCard rail, mock card provider only. Also POST /v1/checkout/sessions/:id/confirm and GET /v1/checkout/sessions/:id.

Every response is sent with Cache-Control: no-store. Errors have the form {"error":"<code>"}, sometimes with a message.

Create a payment intent

curl -X POST https://<base-url>/v1/intents \
  -H "x-api-key: <MERCHANT_KEY>" \
  -H "Idempotency-Key: <unique-id>" \
  -H "content-type: application/json" \
  -d '{"amount":"2000","resourceUrl":"https://api.example.com/report"}'

The amount is a string of atomic units with 6 decimals, so "2000" is 0.002 test USDC. The maximum is 1,000 test USDC.

  • amount (required): atomic units, digits only.
  • resourceUrl (optional): up to 2,048 characters.
  • description (optional): up to 500 characters.
  • payTo (optional): must be one of your registered seller wallets.

The response is 201 for a new intent and 200 for a replay. It contains intentId, status, expiresAt and paymentRequired. An intent lives for 10 minutes.

The payment challenge

paymentRequired holds x402Version 2, a resource with the URL and description, and one entry in accepts:

  • scheme: exact. network: Solana devnet.
  • asset: the devnet test token. amount: atomic units. payTo: the seller’s registered wallet.
  • maxTimeoutSeconds: 60.
  • extra.feePayer: the network-fee payer. extra.memo: the intent id. extra.paymentFlow: upfront.

The seller answers its buyer with HTTP 402, the challenge in the body and the same challenge, base64-encoded, in a PAYMENT-REQUIRED header. The buyer retries with a PAYMENT-SIGNATURE header.

Seller flow

  1. When a request arrives without a payment, call POST /v1/intents and answer 402 with the returned challenge.
  2. When the buyer retries with PAYMENT-SIGNATURE, call POST /verify with {x402Version, paymentPayload, paymentRequirements}.
  3. Call POST /settle with the same body.
  4. Serve the resource only when /settle returns success: true with a transaction signature. If it returns settlement_pending, answer 402 with a Retry-After and let the buyer resend the same header.
  5. Reconcile with GET /v1/payments.

Our demo resource server implements this flow as middleware. It is reference code, not a packaged SDK, and it serves only GET requests.

What the buyer sees from the demo resource server

What the buyer sees from the demo resource server
ResponseMeaning and what to do
402 with a payment challengeNo payment yet. Pay and retry with the PAYMENT-SIGNATURE header.
402 settlement_pendingSettlement is still completing. Wait for Retry-After and resend the same signature. Do not sign again.
402 payment_invalidThe payment was rejected before anything was submitted. A reason is included.
402 payment_failedThe payment did not complete.
409 already_served, payment_in_flight, unknown_intentThe payment was already used, a different payload is in flight, or the intent is unknown.
429 rate_limitedToo many requests. Wait for Retry-After.
503 payments_unavailable, facilitator_unavailable, settlement_indeterminateTemporary problem. Resend the same signature if you already signed one.

Agent (buyer) flow

  1. Request the resource and read the challenge from the PAYMENT-REQUIRED header.
  2. Check the price, the budget and the recipient against your own limits. Our demo agent also refuses anything that is not devnet.
  3. Sign an exact-scheme payment with the official x402 Solana client. Store the signed header before you send it.
  4. Request again with PAYMENT-SIGNATURE.
  5. On settlement_pending, resend the same header after Retry-After. Do not sign a second payment for the same request.
  6. Success is a 200 with a PAYMENT-RESPONSE header.

Statuses

  • Attempt: pending, authorized, submitted, captured, final. The exits are failed, voided (card only), reverted and refunded. Failed, voided, reverted and refunded are terminal.
  • Intent: created, requires_action, processing, succeeded, failed, canceled, reverted. succeeded means released, not merely captured.

What to gate fulfilment on: payment_intent.succeeded, payment_attempt.releasable, or a /settle result of success: true. Do not gate on a bare captured, which can appear before release.

Release: an amount at or below the seller’s threshold is released at captured, and a larger amount at final. The default is 5 test USDC per seller, with a platform ceiling of 50 test USDC. The default serving mode is settle-then-serve. A seller can opt in to serve-then-settle for low-value routes, with an exposure cap.

Webhooks

Events include payment_intent.created, processing, succeeded, failed, canceled and reverted, and payment_attempt.created, authorized, submitted, captured, releasable, final, failed, voided, reverted and refunded, plus reconciliation.break_opened when our records and the network or provider disagree. The body is {id, type, apiVersion, createdAt, data}, where data holds the intent id, attempt id, rail and event details.

  • Headers: AFI-Event-Id (stable across retries), AFI-Delivery-Attempt (starting at 1) and AFI-Signature, in the form t=<unix seconds>,v1=<hex>.
  • The signature is a hex HMAC-SHA256, keyed with your endpoint secret, over the string <t>.<raw request body>.
  • To verify: read the raw body, parse t and v1, recompute the HMAC, compare in constant time, reject a t that is too old, and de-duplicate on AFI-Event-Id. Return 2xx quickly.
  • Delivery is at least once. Failed deliveries retry with a backoff that starts at 1 second and doubles up to 1 hour.
  • Webhook destinations must be https on port 443 at a public address. Redirects are not followed.

The payment object

Each item in GET /v1/payments has: id, status, amount and amountDecimal, asset, channel, rail (x402 or card), attemptStatus, transaction (the Solana signature, or null), releasable, createdAt, payer (null until claimed), idempotencyKey, networkFee (null until recorded), platformFee (null: no fee is recorded today), refunded (true once a card payment has been refunded) and gasPaidBy. Card payments add fields such as the card brand and last four digits, 3-D Secure result and provider reference. The detail route adds the attempts, the timeline and the ledger entries.

Errors

Errors
CodeHTTPMeaning
unauthorized401Missing, unknown or revoked key.
merchant_inactive403The merchant account is suspended.
invalid_request, invalid_json, invalid_amount, amount_too_large400The request body is malformed or the amount is out of range.
idempotency_key_required400POST /v1/intents needs an Idempotency-Key header.
idempotency_conflict409The same Idempotency-Key was used with a different body.
not_found404Unknown route or payment, including another merchant’s payment id.
payload_too_large413The body is larger than 64 KiB.
rate_limited429More than 300 write requests per minute for one merchant. Retry-After: 60.
too_many_concurrent_settles429More than 8 settlements in flight for one merchant. Retry-After: 2.
gas_unavailable503The network-fee account is unable to pay, so no challenge is issued. Retry-After: 5.
internal_error500Unexpected error.

Protocol outcomes, such as an invalid payment, come back as an x402 body with HTTP 200 and an isValid or success flag.

Limits

  • Request body: 64 KiB.
  • Write requests: 300 per minute per merchant. Concurrent settlements: 8 per merchant.
  • Settle wait: up to 10 seconds, then settlement_pending.
  • Amount: up to 1,000 test USDC. Intent lifetime: 10 minutes.

Coming

  • A public base URL and a packaged SDK.
  • Self-service merchant keys, key rotation and webhook endpoint registration, with signing-secret rotation.
  • Seller wallet registration over the API.
  • Refunds and non-delivery claims for x402.
  • An error catalog with request ids.
  • EVM chains, and real money only after the money path is reviewed.

Payzilla is software, not a bank. This is a devnet preview with test tokens only. See the roadmap and how the system is built on the security page.

Get in touch

Found a bug in the preview? Write to [email protected].