Dial x402

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 mainnet
  • payTo: The Dial wallet receiving payment
  • maxTimeoutSeconds: 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

StatusMeaning
400Validation error (bad request body)
402Payment required or payment failed verification
429Rate limited (60 req/min default)
502Provider error (upstream service unavailable)
500Internal server error

On this page