Skip to main content

Error Response Format

All errors follow a consistent format:
{
  "error": {
    "code": "error_code",
    "message": "Human-readable error message",
    "details": {
      "additional": "context"
    }
  }
}

HTTP Status Codes

200
success
Success - Request completed successfully
400
error
Bad Request - Invalid request or business logic error
404
error
Not Found - Resource doesn’t exist
422
error
Validation Error - Request data failed validation
500
error
Internal Error - Something went wrong on our end
502
error
Upstream Error - Error from upstream provider
504
error
Timeout - Request took too long

Error Codes

Client Errors (4xx)

Status: 400Request data is malformed or invalid.
{
  "error": {
    "code": "invalid_request",
    "message": "Transaction hash must be a valid hex string",
    "details": {
      "field": "transaction_hash",
      "provided": "invalid_hash"
    }
  }
}
Common causes:
  • Missing required fields
  • Invalid address format
  • Malformed transaction hash
  • Invalid chain identifier
Status: 404Requested resource doesn’t exist.
{
  "error": {
    "code": "not_found",
    "message": "Verification not found: vrf_invalid123"
  }
}
Common causes:
  • Invalid verification_id
  • Invalid settlement_id
  • Resource was deleted
Status: 400Payment verification failed validation.
{
  "error": {
    "code": "verification_failed",
    "message": "Invalid EIP-3009 signature",
    "details": {
      "reason": "Signature verification failed",
      "recovered_address": "0x...",
      "expected_address": "0x..."
    }
  }
}
Common causes:
  • Invalid signature
  • Wrong signer address
  • Amount mismatch
  • Token address mismatch
  • Authorization expired
Status: 400Merchant hasn’t approved USDC spending for fee collection.
{
  "error": {
    "code": "insufficient_allowance",
    "message": "Merchant has not approved USDC spending for fee collection",
    "details": {
      "required_approval": "0.001",
      "current_allowance": "0",
      "treasury_address": "0xa92560dcaf2fb556dfee4d2599f021a511b94aae",
      "merchant_address": "0xa821f428ef8cc9f54a9915336a82220853059090"
    }
  }
}
Solution:
# Approve USDC spending
python -m app.merchant_approval --network base-sepolia --amount 1000
Status: 400Settlement execution failed.
{
  "error": {
    "code": "settlement_failed",
    "message": "FiatTokenV2: authorization is expired",
    "details": {
      "reason": "Authorization validBefore timestamp has passed",
      "validBefore": "1735689600",
      "currentTime": "1735780000"
    }
  }
}
Common causes:
  • Authorization expired
  • Authorization already used
  • Insufficient customer balance
  • Invalid authorization parameters
Status: 422Request data failed validation.
{
  "error": {
    "code": "validation_error",
    "message": "Validation failed",
    "details": {
      "field": "expected_amount",
      "error": "Must be a positive integer string"
    }
  }
}

Server Errors (5xx)

Status: 500Internal server error.
{
  "error": {
    "code": "internal_error",
    "message": "An internal error occurred"
  }
}
Action: Retry the request after a short delay.
Status: 502Error from upstream provider.
{
  "error": {
    "code": "upstream_error",
    "message": "API unavailable",
    "details": {
      "upstream_status": 503
    }
  }
}
Action: Retry with exponential backoff.
Status: 504Request timeout.
{
  "error": {
    "code": "timeout",
    "message": "Request timeout after 30 seconds"
  }
}
Action: Retry the request.

EIP-3009 Specific Errors

Authorization Expired

{
  "error": {
    "code": "settlement_failed",
    "message": "FiatTokenV2: authorization is expired",
    "details": {
      "reason": "Authorization validBefore timestamp has passed",
      "validBefore": "1735689600",
      "currentTime": "1735780000"
    }
  }
}
Solution: User must create new authorization with longer validity:
// ❌ Bad: 1 hour validity
validBefore: String(Math.floor(Date.now() / 1000) + 3600);

// ✅ Good: 24 hour validity
validBefore: String(Math.floor(Date.now() / 1000) + 86400);

Authorization Already Used

{
  "error": {
    "code": "settlement_failed",
    "message": "FiatTokenV2: authorization is used or canceled",
    "details": {
      "reason": "This nonce has already been used",
      "nonce": "0xee232e308a6647938a68aee1ae355b85a262e4e450b7fde55cd4e729e6444ce9"
    }
  }
}
Solution: Generate unique nonce for each payment:
// ✅ Generate unique nonce
const nonceBytes = new Uint8Array(32);
window.crypto.getRandomValues(nonceBytes);
const nonce =
  "0x" +
  Array.from(nonceBytes)
    .map((b) => b.toString(16).padStart(2, "0"))
    .join("");

Invalid Signature

{
  "error": {
    "code": "verification_failed",
    "message": "Invalid EIP-3009 signature",
    "details": {
      "reason": "Recovered signer does not match from address",
      "recovered": "0x1234...",
      "expected": "0x5678..."
    }
  }
}
Solution: Ensure signature is created correctly:
// Use eth_signTypedData_v4 for EIP-712
const signature = await window.ethereum.request({
  method: "eth_signTypedData_v4",
  params: [userAddress, JSON.stringify(typedData)],
});

Error Handling Flow

Error Handling Best Practices

async function handlePayment(authorization, signature) {
  try {
    // 1. Verify payment
    const verifyResponse = await fetch(
      "https://facilitator.0xmeta.ai/v1/verify",
      {
        method: "POST",
        headers: { "Content-Type": "application/json" },
        body: JSON.stringify({
          transaction_hash: authorization.nonce,
          chain: "base-sepolia",
          seller_address: merchantAddress,
          expected_amount: authorization.value,
          expected_token: authorization.token,
          metadata: {
            source: "my_app",
            paymentPayload: {
              x402Version: 1,
              scheme: "exact",
              network: "base-sepolia",
              payload: { authorization, signature },
            },
          },
        }),
      }
    );

    if (!verifyResponse.ok) {
      const error = await verifyResponse.json();
      handleVerificationError(error);
      return;
    }

    const { verification_id } = await verifyResponse.json();

    // 2. Settle payment
    const settleResponse = await fetch(
      "https://facilitator.0xmeta.ai/v1/settle",
      {
        method: "POST",
        headers: { "Content-Type": "application/json" },
        body: JSON.stringify({
          verification_id,
          destination_address: merchantAddress,
        }),
      }
    );

    if (!settleResponse.ok) {
      const error = await settleResponse.json();
      handleSettlementError(error);
      return;
    }

    const settlement = await settleResponse.json();
    console.log("Settlement initiated:", settlement.settlement_id);
  } catch (error) {
    console.error("Network error:", error);
    // Retry logic here
  }
}

function handleVerificationError(error) {
  switch (error.error.code) {
    case "verification_failed":
      if (error.error.details?.reason?.includes("expired")) {
        alert("Authorization expired. Please try again.");
      } else if (error.error.details?.reason?.includes("signature")) {
        alert("Invalid signature. Please reconnect your wallet.");
      }
      break;

    case "invalid_request":
      console.error("Invalid request:", error.error.details);
      break;

    default:
      alert("Verification failed: " + error.error.message);
  }
}

function handleSettlementError(error) {
  switch (error.error.code) {
    case "insufficient_allowance":
      alert(
        "Merchant setup required. Please contact support.\n\n" +
          "Treasury: " +
          error.error.details.treasury_address
      );
      break;

    case "settlement_failed":
      if (error.error.message.includes("expired")) {
        alert("Payment authorization expired. Please try again.");
      } else if (error.error.message.includes("used or canceled")) {
        alert("This payment has already been processed.");
      }
      break;

    case "not_found":
      alert("Verification not found. Please start over.");
      break;

    default:
      alert("Settlement failed: " + error.error.message);
  }
}

Retry Strategy

For transient errors (5xx, network errors), implement exponential backoff:
async function retryRequest(fn, maxRetries = 3) {
  for (let attempt = 0; attempt < maxRetries; attempt++) {
    try {
      return await fn();
    } catch (error) {
      const shouldRetry =
        error.status >= 500 ||
        error.code === "upstream_error" ||
        error.code === "timeout" ||
        error.code === "ETIMEDOUT";

      if (!shouldRetry || attempt === maxRetries - 1) {
        throw error;
      }

      // Exponential backoff: 1s, 2s, 4s
      const delay = Math.pow(2, attempt) * 1000;
      console.log(`Retry attempt ${attempt + 1} after ${delay}ms`);
      await new Promise((resolve) => setTimeout(resolve, delay));
    }
  }
}

// Usage
try {
  const result = await retryRequest(() => verifyPayment(data));
} catch (error) {
  console.error("All retry attempts failed:", error);
}
Don’t retry on 4xx errors (except possibly insufficient_allowance after fixing). These indicate problems with your request that won’t be fixed by retrying.

Error Prevention Checklist

1

Before Verification

  • ✅ Generate unique random nonce
  • ✅ Set validBefore to 24+ hours
  • ✅ Use correct token address for network
  • ✅ Format amounts correctly (no decimals in amount string)
  • ✅ Include complete paymentPayload in metadata
2

Before Settlement

  • ✅ Merchant has approved USDC spending - ✅ Verification status is “verified” - ✅ Authorization hasn’t expired - ✅ Nonce hasn’t been used before
3

Error Handling

  • ✅ Catch and handle all error codes
  • ✅ Show user-friendly error messages
  • ✅ Log errors for debugging
  • ✅ Implement retry logic for 5xx errors
  • ✅ Don’t retry 4xx errors automatically

Common Error Scenarios

ScenarioError CodeSolution
Merchant not approvedinsufficient_allowanceRun approval setup
Authorization expiredsettlement_failedCreate new authorization
Nonce reusedsettlement_failedGenerate new unique nonce
Invalid signatureverification_failedFix signature creation
Wrong amountverification_failedMatch expected_amount exactly
Network issuesupstream_error / timeoutRetry with backoff
Most errors are preventable! Follow best practices for nonce generation, authorization validity, and merchant setup.
Log all errors with full context (verification_id, settlement_id, timestamps) to help with debugging and support requests.