Verify Payment
curl --request POST \
--url https://api.example.com/v1/verifyimport requests
url = "https://api.example.com/v1/verify"
response = requests.post(url)
print(response.text)const options = {method: 'POST'};
fetch('https://api.example.com/v1/verify', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));<?php
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_URL => "https://api.example.com/v1/verify",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "POST",
]);
$response = curl_exec($curl);
$err = curl_error($curl);
curl_close($curl);
if ($err) {
echo "cURL Error #:" . $err;
} else {
echo $response;
}package main
import (
"fmt"
"net/http"
"io"
)
func main() {
url := "https://api.example.com/v1/verify"
req, _ := http.NewRequest("POST", url, nil)
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
body, _ := io.ReadAll(res.Body)
fmt.Println(string(body))
}HttpResponse<String> response = Unirest.post("https://api.example.com/v1/verify")
.asString();require 'uri'
require 'net/http'
url = URI("https://api.example.com/v1/verify")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Post.new(url)
response = http.request(request)
puts response.read_body{
"isValid": true,
"message": "Payment authorization verified successfully",
"details": {
"verification_id": "vrf_b183c9692fac4481",
"verified_amount": "20000",
"verified_token": "0x036cbd53842c5426634e7929541ec2318f3dcf7e",
"merchant_address": "0xa821f428ef8cc9f54a9915336a82220853059090",
"payer_address": "0x78b6b8c55d5ea96be6ca8213e61b0fa53e862480",
"verified_at": "2025-01-23T10:42:40Z"
}
}
{
"error": {
"code": "verification_failed",
"message": "Invalid EIP-3009 signature",
"details": {
"reason": "Signature verification failed",
"recovered": "0x1234...",
"expected": "0x78b6..."
}
}
}
{
"error": {
"code": "verification_failed",
"message": "Payment must be to merchant address",
"details": {
"authorization_to": "0x5d79...",
"expected_payTo": "0xA821...",
"reason": "Customer must authorize payment to merchant, not facilitator"
}
}
}
{
"error": {
"code": "verification_failed",
"message": "Amount mismatch",
"details": {
"authorization_value": "10000",
"expected_amount": "20000"
}
}
}
{
"error": {
"code": "verification_failed",
"message": "Authorization expired",
"details": {
"validBefore": "1735689600",
"currentTime": "1735780000"
}
}
}
API Reference
Verify Payment
Verify an EIP-3009 payment authorization
POST
/
v1
/
verify
Verify Payment
curl --request POST \
--url https://api.example.com/v1/verifyimport requests
url = "https://api.example.com/v1/verify"
response = requests.post(url)
print(response.text)const options = {method: 'POST'};
fetch('https://api.example.com/v1/verify', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));<?php
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_URL => "https://api.example.com/v1/verify",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "POST",
]);
$response = curl_exec($curl);
$err = curl_error($curl);
curl_close($curl);
if ($err) {
echo "cURL Error #:" . $err;
} else {
echo $response;
}package main
import (
"fmt"
"net/http"
"io"
)
func main() {
url := "https://api.example.com/v1/verify"
req, _ := http.NewRequest("POST", url, nil)
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
body, _ := io.ReadAll(res.Body)
fmt.Println(string(body))
}HttpResponse<String> response = Unirest.post("https://api.example.com/v1/verify")
.asString();require 'uri'
require 'net/http'
url = URI("https://api.example.com/v1/verify")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Post.new(url)
response = http.request(request)
puts response.read_body{
"isValid": true,
"message": "Payment authorization verified successfully",
"details": {
"verification_id": "vrf_b183c9692fac4481",
"verified_amount": "20000",
"verified_token": "0x036cbd53842c5426634e7929541ec2318f3dcf7e",
"merchant_address": "0xa821f428ef8cc9f54a9915336a82220853059090",
"payer_address": "0x78b6b8c55d5ea96be6ca8213e61b0fa53e862480",
"verified_at": "2025-01-23T10:42:40Z"
}
}
{
"error": {
"code": "verification_failed",
"message": "Invalid EIP-3009 signature",
"details": {
"reason": "Signature verification failed",
"recovered": "0x1234...",
"expected": "0x78b6..."
}
}
}
{
"error": {
"code": "verification_failed",
"message": "Payment must be to merchant address",
"details": {
"authorization_to": "0x5d79...",
"expected_payTo": "0xA821...",
"reason": "Customer must authorize payment to merchant, not facilitator"
}
}
}
{
"error": {
"code": "verification_failed",
"message": "Amount mismatch",
"details": {
"authorization_value": "10000",
"expected_amount": "20000"
}
}
}
{
"error": {
"code": "verification_failed",
"message": "Authorization expired",
"details": {
"validBefore": "1735689600",
"currentTime": "1735780000"
}
}
}
Endpoint
Verify that an EIP-3009transferWithAuthorization signature is valid and meets expected criteria.
Key principle: Customer authorizes payment to merchant address (not facilitator).
Verification is FREE. Only successful settlements incur the $0.01 fee.
Request Parameters
object
required
Payment payload containing EIP-3009 authorization and signaturepayment.payload.authorization:
from: Customer addressto: MERCHANT address (where customer payment goes)value: Payment amount in token’s smallest unitvalidAfter: Unix timestamp (usually “0”)validBefore: Unix timestamp (expiry)nonce: Unique random bytes32
object
required
Payment requirements from merchant’s 402 response
scheme: “exact”price: “$0.02” (or atomic amount)network: “eip155:84532” (CAIP-2 format)payTo: Merchant addresstoken: USDC address (optional if using price)
Request Examples
curl -X POST https://facilitator.0xmeta.ai/v1/verify \
-H "Content-Type: application/json" \
-d '{
"payment": {
"x402Version": 2,
"payload": {
"authorization": {
"from": "0x78b6b8c55d5ea96be6ca8213e61b0fa53e862480",
"to": "0xA821f428Ef8cC9f54A9915336A82220853059090",
"value": "20000",
"validAfter": "0",
"validBefore": "1735689600",
"nonce": "0xee232e308a6647938a68aee1ae355b85a262e4e450b7fde55cd4e729e6444ce9"
},
"signature": "0x1e112204abec0856f56559b2d9a58d4e7bb5daec..."
}
},
"requirements": {
"scheme": "exact",
"price": "$0.02",
"network": "eip155:84532",
"payTo": "0xA821f428Ef8cC9f54A9915336A82220853059090",
"token": "0x036CbD53842c5426634e7929541eC2318f3dCF7e"
}
}'
const response = await fetch("https://facilitator.0xmeta.ai/v1/verify", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
payment: {
x402Version: 2,
payload: {
authorization: {
from: customerAddress,
to: merchantAddress, // ← YOUR address
value: "20000", // $0.02 USDC
validAfter: "0",
validBefore: String(Math.floor(Date.now() / 1000) + 86400),
nonce: randomNonce(),
},
signature: signature
}
},
requirements: {
scheme: "exact",
price: "$0.02",
network: "eip155:84532",
payTo: merchantAddress, // ← YOUR address
token: "0x036CbD53842c5426634e7929541eC2318f3dCF7e"
}
})
});
const data = await response.json();
console.log(data.isValid); // true
import requests
response = requests.post(
'https://facilitator.0xmeta.ai/v1/verify',
headers={'Content-Type': 'application/json'},
json={
'payment': {
'x402Version': 2,
'payload': {
'authorization': {
'from': customer_address,
'to': merchant_address, # YOUR address
'value': '20000',
'validAfter': '0',
'validBefore': str(int(time.time()) + 86400),
'nonce': random_nonce()
},
'signature': signature
}
},
'requirements': {
'scheme': 'exact',
'price': '$0.02',
'network': 'eip155:84532',
'payTo': merchant_address, # YOUR address
'token': '0x036CbD53842c5426634e7929541eC2318f3dCF7e'
}
}
)
data = response.json()
print(data['isValid'])
Response Examples
{
"isValid": true,
"message": "Payment authorization verified successfully",
"details": {
"verification_id": "vrf_b183c9692fac4481",
"verified_amount": "20000",
"verified_token": "0x036cbd53842c5426634e7929541ec2318f3dcf7e",
"merchant_address": "0xa821f428ef8cc9f54a9915336a82220853059090",
"payer_address": "0x78b6b8c55d5ea96be6ca8213e61b0fa53e862480",
"verified_at": "2025-01-23T10:42:40Z"
}
}
{
"error": {
"code": "verification_failed",
"message": "Invalid EIP-3009 signature",
"details": {
"reason": "Signature verification failed",
"recovered": "0x1234...",
"expected": "0x78b6..."
}
}
}
{
"error": {
"code": "verification_failed",
"message": "Payment must be to merchant address",
"details": {
"authorization_to": "0x5d79...",
"expected_payTo": "0xA821...",
"reason": "Customer must authorize payment to merchant, not facilitator"
}
}
}
{
"error": {
"code": "verification_failed",
"message": "Amount mismatch",
"details": {
"authorization_value": "10000",
"expected_amount": "20000"
}
}
}
{
"error": {
"code": "verification_failed",
"message": "Authorization expired",
"details": {
"validBefore": "1735689600",
"currentTime": "1735780000"
}
}
}
Response Fields
boolean
required
Whether the payment authorization is valid
string
required
Human-readable verification result
object
details.verification_id: Use this for settlementdetails.verified_amount: Confirmed payment amountdetails.merchant_address: Merchant receiving paymentdetails.payer_address: Customer making paymentdetails.verified_at: ISO 8601 timestamp
Verification Process
What Gets Verified
1
Signature Validation
EIP-712 signature cryptographically verified using
ecrecover2
Recipient Validation
Critical: Authorization
to address must match merchant address from requirements.payTo✅ authorization.to == requirements.payTo (merchant address)❌ authorization.to == treasury_address (wrong!)3
Amount Validation
Authorization value matches expected amount exactly
4
Token Validation
Token address matches expected token (if specified)
5
Expiry Check
Current time is between
validAfter and validBefore6
Nonce Uniqueness
Nonce hasn’t been used in a previous settlement
Important Notes
Customer must authorize to MERCHANT address, not facilitator.The facilitator will reject authorizations to the treasury address. This maintains x402 trust-minimization.
Verification is FREE - unlimited verifications at no cost. Only successful settlements incur the $0.01 fee (collected from merchant’s approved balance).
Save the
verification_id - you’ll need it for settlement! Store it with your order/transaction record.Common Use Cases
USDC Payment on Base Sepolia
USDC Payment on Base Sepolia
{
"payment": {
"payload": {
"authorization": {
"to": "0xA821f428Ef8cC9f54A9915336A82220853059090", // Merchant
"value": "20000", // $0.02
...
}
}
},
"requirements": {
"payTo": "0xA821f428Ef8cC9f54A9915336A82220853059090", // Same merchant
"token": "0x036CbD53842c5426634e7929541eC2318f3dCF7e" // USDC Sepolia
}
}
USDC Payment on Base Mainnet
USDC Payment on Base Mainnet
{
"requirements": {
"price": "$0.02",
"network": "eip155:8453", // Base Mainnet
"payTo": "0x...", // Merchant address
"token": "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913" // USDC Mainnet
}
}
Error Prevention
1
Use Merchant Address
Always set
authorization.to = merchant address (not treasury)2
Generate Unique Nonce
const nonce = "0x" + Array.from(crypto.getRandomValues(new Uint8Array(32)))
.map(b => b.toString(16).padStart(2, "0"))
.join("");
3
Set Long Validity
validBefore: String(Math.floor(Date.now() / 1000) + 86400) // 24 hours
4
Match Amounts Exactly
Ensure
authorization.value matches requirements.price or amountNext Steps
Settle Payment
Execute settlement after verification
Error Handling
Handle verification failures
Architecture
Understand payment flow
Integration Guide
Complete setup guide
Verification complete! Use the
verification_id to settle the payment.