Skip to main content
Every release test runs the same five checks, in the same order, against one authorized route. Each check answers one question a paying customer would ask, records the evidence behind its answer, and reports a status and a code. The verdict is computed from these five results and nothing else.

How a run is ordered

Not every check always runs:
  • If handshake is not PASS, Selo pays nothing. The other four checks report NOT_RUN and the run concludes.
  • response_contract runs only when the paid request came back with a response. If your endpoint rejected the signed payment with a 402, or the paid request never completed, it reports NOT_RUN.
  • retry_safety runs only after paid_delivery recorded a settlement, no payment is held as unresolved, and the authorization still covers the route.
Before the run, the free preflight sent one unpaid probe of its own. During the run, every request Selo sends to your endpoint carries an Idempotency-Key header holding an operation id. See Payments and spend limits.

Check results

Each check produces one result object. The same shape appears in the release test response and the private report. The signed attestation carries every field except summary. Two codes can appear on any check, set by the runner rather than the check itself:

handshake

What it proves. Your endpoint asks for payment correctly. An unpaid request gets a 402 with a decodable x402 v2 challenge that offers a payment Selo can make: the exact scheme, USDC on Algorand Mainnet, a positive price and a valid payee. How Selo measures it.
  1. Sends the authorized method to the authorized URL, with the request body from your preflight (POST only), through the SSRF-safe fetch. The timeout is 10 seconds and redirects are not followed.
  2. Reads the challenge from the PAYMENT-REQUIRED response header, a base64-encoded JSON object. The response body is not read.
  3. Requires x402Version: 2 and picks the first accepts entry with scheme: "exact", network algorand:wGHE2Pwdvd7S12BL5FaOP20EGYesN73ktiC1qzkkit8= and asset 31566704.
  4. Requires that entry’s amount to be a positive integer of atomic units (micro-USDC) and its payTo to be a valid Algorand address.
Evidence. One target_challenge record: the status, content type, sanitized response headers, the decoded challenge and any decode error. Its SHA-256 is signed into the attestation as targetChallengeSha256. What it proves. A paying customer gets what they paid for. Selo pays your price on chain and your endpoint answers with a 2xx and a settlement that Selo can match to the transfer it signed. How Selo measures it.
1

Check its own wallet

Selo reads its operating wallet before signing anything. If the wallet is below its ALGO minimum balance, is not opted in to USDC, or holds less USDC than your price, or if the Algorand node does not answer within 5 seconds, nothing is signed.
2

Reserve the amount

The spend guard reserves exactly your price in one conditional database update. A denial ends the check without signing.
3

Sign exactly the reserved requirement

Selo’s x402 client signs only the accepts entry whose scheme, network, asset, amount and payTo match the reservation, with the client’s spend cap set to that amount. The entry must also carry maxTimeoutSeconds and an extra object, and the challenge a resource.url. If your challenge declares the x402 payment-identifier extension, Selo adds a payment id derived from the operation id.
4

Send one paid request

The request carries the PAYMENT-SIGNATURE header, Idempotency-Key: <jobId>:paid_delivery and the same body as the probe. The timeout is 20 seconds, redirects are not followed, and the response body is read up to 256 KiB.
5

Match the settlement

Selo reads the PAYMENT-RESPONSE header. A settlement with success: true and a non-empty transaction is your claim. Selo computes the id of the transfer it signed and records the payment as settled only when the two match.
Whenever a signed payment has left Selo without a settlement Selo can confirm, the payment is held UNRESOLVED. That covers PAID_REQUEST_REJECTED, PAYER_REJECTED, SETTLEMENT_UNCONFIRMED, SETTLEMENT_TX_MISMATCH and PAYMENT_UNRESOLVED. The amount keeps counting against the budget and Selo stops spending for the job. See Held payments and reconciliation.
Evidence. A paid_response record (status, content type, sanitized headers, claimed and settled transaction ids, the result of the transaction match, body SHA-256 and size, the first 2,048 characters of the body, and the SHA-256 of the payment header) and a downstream_payment record (the payment row: status, amount, network, asset, payee, transaction ids, timestamps and resolution reason).

response_contract

What it proves. The response a paying customer receives is the one you promised. This is the check that blocks a release whose health check is green but whose paid response is wrong. How Selo measures it. It evaluates the response from paid_delivery. No new request is sent. The contract you send with the release test has three optional fields; see Write a release contract. Selo compares in a fixed order (status, then content type, then JSON parsing, then the schema) and reports the first failure. For a schema failure, the summary carries the first validation error, truncated to 200 characters. On Algorand Mainnet, the demo merchant’s broken-v1 release paid and answered 200, and this check failed it with SCHEMA_MISMATCH: “Response body did not match the expected schema: must have required property ‘receiptId’”. See Proof on Algorand Mainnet. Evidence. The paid_response record from paid_delivery, plus a response_contract record with the expected contract, the observed status, content type, body SHA-256 and size, and the resulting code. The contract’s SHA-256 is signed into the attestation as expectedContractSha256.

discovery_contract

What it proves. What buyers and agents discover about your route agrees with what your endpoint actually does: the same method, URL, payee and price, with enough metadata to use it. How Selo measures it.
  1. Reads the Bazaar discovery info from the live challenge (extensions.bazaar.info). If it declares input.method and that method differs from the tested method, the check fails at once.
  2. Looks your route up in the Bazaar catalog, scoped by payee: GET https://facilitator.goplausible.xyz/discovery/resources?payTo=<your payTo>&limit=100&offset=<n>. Selo pages through at most 20 pages, with 10 seconds per page and 20 seconds overall. A record matches when its resourceUrl equals the tested URL and its method equals the tested method.
  3. Compares a matching record with the live requirement: method, URL, an accepts entry with the same network, asset and payTo, and the same amount.
  4. Checks that a description, discovery info and an output example are present. Each can come from the live challenge or the catalog record.
The lookup is scoped by your payee address. A listing of the same URL under a different payTo is not compared; your route reads as not catalogued.
The metadata Selo reads lives in your decoded challenge, abridged here with placeholder values:
PAYMENT-REQUIRED (decoded, abridged)
Evidence. A catalog_record record: the tested URL and method, the lookup result, the catalog record when found, the reason when the catalog was unavailable, the live discovery info and the resulting code.

retry_safety

What it proves. A client that retries a paid request with the same signed payment is not charged twice or served a second, different result. How Selo measures it. Selo first re-checks that the authorization is still verified, unexpired and covering the route. It then sends the identical PAYMENT-SIGNATURE header and the identical body again, with the same Idempotency-Key as the paid request (<jobId>:paid_delivery). The timeout is 20 seconds, and the replay body is read up to 1 MiB so it can be compared byte for byte with the paid response. The outcome name is the check’s code, and each outcome maps to exactly one status: A replay that reports a second settlement also raises an operator alert. When the replay does not run, the check reports NOT_RUN. If the reason is that the authorization lapsed after the paid call, the run also records AUTHORIZATION_LAPSED.
Two behaviors pass: reject a replayed payment that already settled with a 4xx, or return the stored result (the same settlement transaction id or a byte-identical body). The demo merchant’s fixed-v2 release answered the replay with 402 and passed with PASS_REPLAY_REJECTED.
Evidence. A replay_response record (the idempotency key, the original transaction id, the replay’s status, transaction id, sanitized headers, body SHA-256 and size, and whether the body matched the paid response) and an authorization_snapshot taken just before the replay.

The job wall clock

A job has 60 seconds from the moment Selo starts running it. When the clock runs out, requests in flight are aborted, and paid_delivery, discovery_contract and retry_safety report NOT_RUN if they have not started. The run records TARGET_TIMEOUT as its reason unless it already recorded another one, and the verdict is INCONCLUSIVE unless a check already proved a FAIL.

Verdicts and the gate

How five results become PASS, FAIL or INCONCLUSIVE, and how the CLI turns that into an exit code.

Write a release contract

Declare the status, content type and JSON schema your paid response must meet.

Payments and spend limits

The reservation, the payment states and the budget behind paid_delivery.

Errors and reason codes

Every error and reason code the API returns.