Payment Flow
Step-by-step x402 payment flow for Dial endpoints.
Payment Flow
Detailed walkthrough of how an x402 payment works when calling a Dial endpoint.
Step 1: Request Without Payment
curl -X POST https://x402.dial.wtf/api/v1/messages/send \
-H "Content-Type: application/json" \
-d '{"to": "+1234567890", "body": "Hello"}'Step 2: Receive 402 Response
HTTP/1.1 402 Payment Required
Payment-Required: eyJ4NDAyVmVyc2lvbiI6Miwi...Decoded Payment-Required header:
{
"x402Version": 2,
"error": "Payment required for this resource",
"resource": {
"url": "https://x402.dial.wtf/api/v1/messages/send",
"description": "Send SMS (x402 pay-per-request)",
"mimeType": "application/json"
},
"accepts": [
{
"scheme": "exact",
"network": "eip155:8453",
"amount": "10000",
"asset": "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",
"payTo": "0xYourDialPayToAddress",
"maxTimeoutSeconds": 300,
"extra": {
"name": "USD Coin",
"version": "2"
}
}
]
}Key fields:
amount:100000= $0.10 USDC (6 decimal places)network:eip155:8453= Base mainnetpayTo: The Dial wallet receiving paymentmaxTimeoutSeconds: Authorization validity window
Step 3: Sign Authorization
The agent signs an EIP-3009 transferWithAuthorization message using EIP-712 typed data:
const authorization = {
from: agentWalletAddress,
to: payTo,
value: "10000",
validAfter: Math.floor(Date.now() / 1000).toString(),
validBefore: (Math.floor(Date.now() / 1000) + 300).toString(),
nonce: randomBytes32(),
};
const signature = await walletClient.signTypedData({
domain: {
name: "USD Coin",
version: "2",
chainId: 8453,
verifyingContract: "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",
},
types: { TransferWithAuthorization: [...] },
primaryType: "TransferWithAuthorization",
message: authorization,
});Step 4: Resubmit With Payment
curl -X POST https://x402.dial.wtf/api/v1/messages/send \
-H "Content-Type: application/json" \
-H "Payment-Signature: eyJ4NDAyVmVyc2lvbiI6Miwi..." \
-d '{"to": "+1234567890", "body": "Hello"}'The Payment-Signature header contains a base64-encoded PaymentPayload:
{
"x402Version": 2,
"accepted": {
"scheme": "exact",
"network": "eip155:8453",
"amount": "10000",
"asset": "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",
"payTo": "0xDialPayTo",
"maxTimeoutSeconds": 300,
"extra": { "name": "USD Coin", "version": "2" }
},
"payload": {
"signature": "0x...",
"authorization": {
"from": "0xAgentWallet",
"to": "0xDialPayTo",
"value": "10000",
"validAfter": "1711234567",
"validBefore": "1711234867",
"nonce": "0xabc123..."
}
}
}Step 5: Receive Resource + Settlement
HTTP/1.1 200 OK
Payment-Response: eyJzdWNjZXNzIjp0cnVl...
Content-Type: application/json
{
"success": true,
"message": "SMS sent",
"provider": "dial",
"messageIds": [12345678]
}Decoded Payment-Response:
{
"success": true,
"transaction": "0xabc123...def456",
"network": "eip155:8453",
"payer": "0xAgentWallet"
}The transaction field is the on-chain settlement hash — verifiable on BaseScan.
Using the SDK (Automatic)
The @x402/fetch wrapper handles steps 2-4 automatically:
import { wrapFetchWithPayment } from "@x402/fetch";
const paidFetch = wrapFetchWithPayment(fetch, x402Client);
// This handles the 402 → sign → resubmit flow transparently
const res = await paidFetch("https://x402.dial.wtf/api/v1/messages/send", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ to: "+1234567890", body: "Hello" }),
});Error Responses
| Status | Meaning |
|---|---|
400 | Validation error (bad request body) |
402 | Payment required or payment failed verification |
429 | Rate limited (60 req/min default) |
502 | Provider error (upstream service unavailable) |
500 | Internal server error |