> For the complete documentation index, see [llms.txt](https://docs.abapays.com/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.abapays.com/protocols/x402.md).

# x402

{% hint style="info" %}
**Zero setup** — no AbaPay account, ever, for this path.
{% endhint %}

An independent agent — say, an FX agent holding a Celo wallet — never needs to know AbaPay exists in advance. The whole exchange happens over plain HTTP, challenge and response.

## The flow

1. Holds or uses a Celo wallet.
2. Calls AbaPay's quote / pay\_bill endpoint directly.
3. Gets `402 Payment Required` back, with the exact price and asset.
4. Signs an EIP-3009 `transferWithAuthorization` with that same wallet — nothing else.
5. Retries the request with the signed authorization attached.
6. AbaPay settles on Celo via Celo's own x402 facilitator and vends the bill.

{% hint style="warning" %}
**Never required for this path:** an account, Agent Hub, API key, PIN, wallet-signature link.
{% endhint %}

{% hint style="info" %}
**Signing capability and running this flow are two different things.** Any self-custody wallet on Celo can produce the EIP-3009 signature step 4 needs — there's nothing AbaPay-specific about it. But driving the three round trips (price → 402 challenge → signed retry) is a different requirement: a wallet's ordinary send screen doesn't do that on its own today, something has to — `abapay-sdk`, a CLI, or an agent (see [the SDK chapter](/build/sdk.md)). A wallet with no such client gets a plain 402 and nowhere to go with it, so **the challenge response carries a `hint` field** (outside the x402 spec, ignored by any client that doesn't look for it) pointing a person stuck there at the ordinary guided flow instead: [**abapays.com**](https://www.abapays.com) needs no x402 at all — connect, see the live price, approve, sign.
{% endhint %}

{% hint style="info" %}
**Account verification happens after settlement, not before.** For electricity, bank transfer, JAMB, and cable (except Showmax), AbaPay checks the meter/account/smartcard number against the biller only once your payment has actually settled on-chain — not while pricing the 402 challenge. This is deliberate: checking beforehand would let anyone probe that lookup for free with no wallet and no payment. A wrong-but-plausible account number still settles the on-chain transfer, then fails verification and is refunded automatically — so double-check the account details before paying, since a bad one costs a settle-then-refund round trip rather than being caught upfront.
{% endhint %}

## Request & response

`POST /api/pay/x402` — no `X-PAYMENT` header yet:

{% code title="Request — no payment attached" %}

```json
{
  "serviceID": "mtn", "serviceCategory": "AIRTIME",
  "network": "MTN", "billersCode": "08012345678",
  "nairaAmount": 1000, "token": "USDC",
  "blockchain": "CELO", "wallet_address": "0xYourAgentWallet..."
}
```

{% endcode %}

{% code title="← 402 Payment Required (real field names)" %}

```json
{
  "x402Version": 1,
  "error": "Payment required",
  "accepts": [{
    "scheme": "exact",
    "network": "eip155:42220",
    "asset": "0x48065fbBE25f71C9282ddf5e1cD6D6A887483D5e",
    "payTo": "0x5df8aE2B963165b735B18Ca86B1ea448d2AA032C",
    "maxTimeoutSeconds": 86400,
    "extra": { "name": "Tether USD", "version": "1", "primaryType": "TransferWithAuthorization" }
  }],
  "hint": "This is a machine-to-machine x402 endpoint. If your wallet can sign but has no client that runs the challenge/sign/retry sequence, pay this bill instead at https://www.abapays.com — no x402 required there. Full x402 flow: https://docs.abapays.com"
}
```

{% endcode %}

{% code title="Retried with X-PAYMENT: <base64 signed authorization> → 200 OK" %}

```json
{
  "success": true, "status": "SUCCESS",
  "tx_hash": "0x91a3...4f2c", "request_id": "req_...",
  "purchased_code": null, "units": null
}
```

{% endcode %}

USDC, USD₮, and USA₮ all settle via Celo's own x402 facilitator — each implements EIP-3009. Full field reference: [openapi.json](https://agents.abapays.com/openapi.json).

`X-PAYMENT` can be either the flat v1 payload (`scheme` at the top level) or the v2 shape that standard x402 clients send (`scheme` inside `accepted`). Both settle the same way.

Optionally identify your agent with `X-Agent-Name` and `X-Agent-Id` (your ERC-8004 agent ID on Celo). They help us support your payments, and change nothing about how a payment is handled. We also check the paying wallet against the public ERC-8004 and Self Agent ID registries. That is used only by AbaPay's support staff and is never shown publicly.

## What the retry can answer

AbaPay records your payment **before** asking the facilitator to move any money, and checks the settlement on-chain before delivering anything. So the answer to the signed retry tells you exactly whether money moved:

| Response                                                                                       | Money moved?                                  | What to do                                                                 |
| ---------------------------------------------------------------------------------------------- | --------------------------------------------- | -------------------------------------------------------------------------- |
| `200` `status: "SUCCESS"`                                                                      | Yes, bill delivered                           | Done                                                                       |
| `status: "FAILED_VENDING"` with `tx_hash` (`200`, or `400` if the account failed verification) | Yes, delivery failed                          | A refund is queued; don't pay again                                        |
| `202` `status: "TIMEOUT"`, `verifying: true`                                                   | Probably. Settled, not yet confirmed on-chain | **Don't pay again.** It is finished in the background                      |
| `200` `status: "TIMEOUT"` (same `X-PAYMENT` sent twice)                                        | Whatever the first attempt did                | Don't re-sign; the first submission is the one that counts                 |
| `400` `errorCode: "MISSING_BILL_DETAILS"` or `"PAYER_MISMATCH"`                                | **No**                                        | Fix the request (bill fields, or sign with the wallet in `wallet_address`) |
| `503` `retryable: true`                                                                        | **No**                                        | Safe to sign again                                                         |
| `402` with `retryable`                                                                         | No, unless `settled: true`                    | `settled: true` means it moved; never pay again in that case               |

{% hint style="warning" %}
A request with no bill (`serviceID`, `billersCode`, `amount`), or one signed by a different wallet than its `wallet_address`, is refused **before** settlement. Nothing is charged. It used to be settled first and then refunded.
{% endhint %}

## Don't hand-roll the signing

`abapay-sdk`'s `payBillViaX402()` does exactly the three requests and one signature above — pass it a viem account and the bill details, get back the settlement result. See [the SDK chapter](/build/sdk.md).


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation by asking a question.

Perform an HTTP GET request on the following URL with the `ask` and `goal` query parameters:

```
GET https://docs.abapays.com/protocols/x402.md?ask=<question>&goal=<user_goal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is what the user is ultimately trying to achieve, the reason they need the answer. Sharing it helps GitBook give you a better, more relevant answer. A goal is most helpful when it describes the outcome the user wants rather than restating the question. For example, with `ask=how do I create an API token`, a goal like `automate deployments from our CI pipeline` lets GitBook tailor the answer to that use case.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
