AI agent developers and API providers

Aug 4, 2026

Share

Category /

other

9 min read

GOAT Network

Building a Pay-Per-Request AI Service: From HTTP 402 to Verified Delivery

Build a pay-per-request AI inference API with x402 by connecting pricing, 402 requirements, wallet authorization, verification, execution, receipts, retries, and refunds.

scroll

Table of contents

A pay-per-request AI service is not complete when it returns 402 Payment Required. The 402 response only starts the transaction. A production service must also bind a price to a specific resource, let an agent authorize within policy, verify the payment payload, run the AI work exactly once, record delivery, and recover when payment and service outcome diverge.

This article uses a hypothetical text-inference endpoint called POST /v1/summarize to show the full seller-side flow:

define service -> quote request -> return 402 -> receive signed payment
-> verify -> create idempotent job -> run inference -> settle and record
-> return result and receipt -> reconcile failures or refund
define service -> quote request -> return 402 -> receive signed payment
-> verify -> create idempotent job -> run inference -> settle and record
-> return result and receipt -> reconcile failures or refund
define service -> quote request -> return 402 -> receive signed payment
-> verify -> create idempotent job -> run inference -> settle and record
-> return result and receipt -> reconcile failures or refund

The endpoint is hypothetical; the implementation pattern is designed to map onto x402 resource-server SDKs and a merchant's existing order system.

1. Define the Paid Service Before Adding Payment Middleware

Start with a product contract, not a wallet address. For this example, the service accepts a text document and returns a summary:

{
  "service": "summarize-v1",
  "method": "POST",
  "path": "/v1/summarize",
  "input": "text/plain or application/json",
  "output": "application/json",
  "price": "0.01 USDC",
  "delivery": "summary plus usage and receipt reference",
  "timeout": "30 seconds"
}
{
  "service": "summarize-v1",
  "method": "POST",
  "path": "/v1/summarize",
  "input": "text/plain or application/json",
  "output": "application/json",
  "price": "0.01 USDC",
  "delivery": "summary plus usage and receipt reference",
  "timeout": "30 seconds"
}
{
  "service": "summarize-v1",
  "method": "POST",
  "path": "/v1/summarize",
  "input": "text/plain or application/json",
  "output": "application/json",
  "price": "0.01 USDC",
  "delivery": "summary plus usage and receipt reference",
  "timeout": "30 seconds"
}

Define the unit of charge precisely. Is the customer paying for a request, input size, output tokens, model tier, or completed result? A fixed request price is easiest to implement. A variable price needs a maximum authorization, a usage meter, and a rule for calculating the final amount.

Also define what counts as delivery. For a summary API, delivery may mean a valid JSON response with a result ID and usage record. For image generation, it may mean a retrievable object plus a content hash. For a research tool, it may include source metadata and a signed receipt.

Payment cannot repair an ambiguous product contract. If the seller cannot state what the buyer receives, the service is not ready for pay-per-request access.

2. Set the Per-Request Price and Policy

For a fixed-price endpoint, configure one payment requirement per supported network and asset. The requirement should include, at minimum:

  • scheme;

  • network identifier;

  • amount or price;

  • receiving address;

  • resource description;

  • expiration or validity window;

  • accepted extensions or receipt format.

Example route configuration, using illustrative values:

const summarizeRoute = {
  method: "POST",
  path: "/v1/summarize",
  accepts: [
    {
      scheme: "exact",
      price: "$0.01",
      network: "eip155:84532",
      payTo: process.env.SELLER_ADDRESS,
    },
  ],
  description: "One text summary using summarize-v1",
  mimeType: "application/json",
};
const summarizeRoute = {
  method: "POST",
  path: "/v1/summarize",
  accepts: [
    {
      scheme: "exact",
      price: "$0.01",
      network: "eip155:84532",
      payTo: process.env.SELLER_ADDRESS,
    },
  ],
  description: "One text summary using summarize-v1",
  mimeType: "application/json",
};
const summarizeRoute = {
  method: "POST",
  path: "/v1/summarize",
  accepts: [
    {
      scheme: "exact",
      price: "$0.01",
      network: "eip155:84532",
      payTo: process.env.SELLER_ADDRESS,
    },
  ],
  description: "One text summary using summarize-v1",
  mimeType: "application/json",
};

The network and asset in this snippet are illustrative test configuration, not a production recommendation. The seller must verify current facilitator support, token configuration, receiving addresses, and deployment environment before accepting real funds.

The client should also have a policy before it signs:

maxPerRequest = $0.05
allowedServices = ["summarize-v1"]
allowedRecipients = [approvedSellerAddress]
allowedNetworks = [approvedNetwork]
requiresHumanApprovalAbove = $1.00
maxPerRequest = $0.05
allowedServices = ["summarize-v1"]
allowedRecipients = [approvedSellerAddress]
allowedNetworks = [approvedNetwork]
requiresHumanApprovalAbove = $1.00
maxPerRequest = $0.05
allowedServices = ["summarize-v1"]
allowedRecipients = [approvedSellerAddress]
allowedNetworks = [approvedNetwork]
requiresHumanApprovalAbove = $1.00

The server declares what it accepts. The agent decides whether that specific offer is authorized. Neither side should assume that the other side's configuration is a substitute for its own controls.

3. Return an x402 Payment Requirement

The first unauthenticated request should not run inference. The resource server returns HTTP 402 and machine-readable payment requirements, commonly through the PAYMENT-REQUIRED header and response metadata.

Illustrative response:

HTTP/1.1 402 Payment Required
Content-Type: application/json
PAYMENT-REQUIRED: <base64-encoded-payment-requirements>

{
  "error"

HTTP/1.1 402 Payment Required
Content-Type: application/json
PAYMENT-REQUIRED: <base64-encoded-payment-requirements>

{
  "error"

HTTP/1.1 402 Payment Required
Content-Type: application/json
PAYMENT-REQUIRED: <base64-encoded-payment-requirements>

{
  "error"

The response should be deterministic enough for an agent to compare against its policy. The agent should verify the method, path, amount, asset, network, recipient, expiry, and service description before signing.

For stronger offer binding, x402's signed offer and receipt extension can sign the payment terms in the 402 response and issue a signed receipt after a successful response. That is useful when the merchant needs evidence that the offer and delivery were tied to the same resource.

Do not treat a 402 response as a payment authorization. It is a quote or requirement. The wallet still needs to approve the transaction, and the server still needs to verify the resulting payload.

4. Let the Agent Authorize the Payment

The client parses the requirement, checks its policy, and creates a payment payload using a wallet adapter. The private key should remain inside a wallet or signing boundary; the language model should receive an action result, not raw key material.

Conceptual client flow:

const firstResponse = await fetch(url, request);

if (firstResponse.status !== 402) {
  return firstResponse;
}

const requirements = readPaymentRequirements(firstResponse);
assertAllowed(requirements, {
  maxAmount: "0.05",
  recipient: approvedSellerAddress,
  networks: [approvedNetwork],
  resource: "/v1/summarize",
});

const paymentPayload = await wallet.signPayment(requirements);

const paidResponse = await fetch(url, {
  ...request,
  headers: {
    ...request.headers,
    "PAYMENT-SIGNATURE": encode(paymentPayload),
  },
});
const firstResponse = await fetch(url, request);

if (firstResponse.status !== 402) {
  return firstResponse;
}

const requirements = readPaymentRequirements(firstResponse);
assertAllowed(requirements, {
  maxAmount: "0.05",
  recipient: approvedSellerAddress,
  networks: [approvedNetwork],
  resource: "/v1/summarize",
});

const paymentPayload = await wallet.signPayment(requirements);

const paidResponse = await fetch(url, {
  ...request,
  headers: {
    ...request.headers,
    "PAYMENT-SIGNATURE": encode(paymentPayload),
  },
});
const firstResponse = await fetch(url, request);

if (firstResponse.status !== 402) {
  return firstResponse;
}

const requirements = readPaymentRequirements(firstResponse);
assertAllowed(requirements, {
  maxAmount: "0.05",
  recipient: approvedSellerAddress,
  networks: [approvedNetwork],
  resource: "/v1/summarize",
});

const paymentPayload = await wallet.signPayment(requirements);

const paidResponse = await fetch(url, {
  ...request,
  headers: {
    ...request.headers,
    "PAYMENT-SIGNATURE": encode(paymentPayload),
  },
});

The client must distinguish a fresh payment from a retry. A payment identifier or idempotency key should travel with the request so the seller can return the same result instead of charging twice after a timeout.

Authorization should cover more than amount. A service that changes state, calls an external system, or triggers a purchase needs stricter controls than a read-only summary endpoint.

5. Verify the Payment Payload Before Running AI Work

The resource server extracts the payment signature and verifies it locally or through an x402 facilitator. The facilitator is a verification and settlement service; it can reduce the seller's blockchain infrastructure burden, but it does not decide whether the business result is correct.

Verification should check that:

  • the signature is valid;

  • the scheme is supported;

  • the network matches the route;

  • the amount meets the requirement;

  • the receiving address is correct;

  • the payload has not expired or been replayed;

  • the payment identifier is not already consumed;

  • the payer has not exceeded local risk or access rules.

Illustrative server boundary:

const payment = extractPaymentSignature(req);
const order = await orders.findOrCreate({
  idempotencyKey: req.header("Idempotency-Key"),
  resource: "/v1/summarize",
  requestHash: hashRequest(req.body),
});

if (order.status === "delivered") {
  return res.status(200).json(order.cachedResponse);
}

const verification = await facilitator.verify(payment, summarizeRoute);

if (!verification.isValid) {
  return res.status(402).json({
    error: "invalid_payment",
    orderId: order.id,
  });
}
const payment = extractPaymentSignature(req);
const order = await orders.findOrCreate({
  idempotencyKey: req.header("Idempotency-Key"),
  resource: "/v1/summarize",
  requestHash: hashRequest(req.body),
});

if (order.status === "delivered") {
  return res.status(200).json(order.cachedResponse);
}

const verification = await facilitator.verify(payment, summarizeRoute);

if (!verification.isValid) {
  return res.status(402).json({
    error: "invalid_payment",
    orderId: order.id,
  });
}
const payment = extractPaymentSignature(req);
const order = await orders.findOrCreate({
  idempotencyKey: req.header("Idempotency-Key"),
  resource: "/v1/summarize",
  requestHash: hashRequest(req.body),
});

if (order.status === "delivered") {
  return res.status(200).json(order.cachedResponse);
}

const verification = await facilitator.verify(payment, summarizeRoute);

if (!verification.isValid) {
  return res.status(402).json({
    error: "invalid_payment",
    orderId: order.id,
  });
}

Do not invoke the model before verification. A rejected payment should not consume expensive inference capacity, and an invalid payload should not create a fulfillment record that looks paid.

6. Execute the Service Exactly Once Per Order

After a valid payment authorization, the service creates or resumes a durable job. The job state should be explicit:

created -> payment_verified -> executing -> result_ready
                    |                |
                    |                -> execution_failed
                    -> payment_failed
created -> payment_verified -> executing -> result_ready
                    |                |
                    |                -> execution_failed
                    -> payment_failed
created -> payment_verified -> executing -> result_ready
                    |                |
                    |                -> execution_failed
                    -> payment_failed

The same idempotency key must map to the same logical job. A repeated HTTP request with the same key should return the existing result, current status, or a controlled failure. It should not start a second inference simply because the client did not receive the first response.

The inference worker should record:

  • model and version;

  • normalized input hash;

  • output hash or result ID;

  • usage measured;

  • start and completion times;

  • execution status;

  • error category;

  • order and payment identifiers.

Keep sensitive input and output data separate from payment metadata where possible. The payment record needs enough information to reconcile the transaction, not necessarily the user's full prompt.

For variable-cost inference, the server should not charge more than the amount the client authorized. x402's usage-based schemes can represent a maximum and settle the actual usage where supported. If the service cannot calculate usage safely, use a fixed price or reject the request before execution.

7. Settle and Return the Result With Evidence

The exact ordering between execution and settlement depends on the x402 scheme and server integration. The invariant is more important than one universal sequence:

  1. No work starts before a valid payment authorization.

  2. The service does not report success before the business result is ready.

  3. The payment, order, and delivery records are linked.

  4. The client can distinguish a delivered result from a payment that is merely pending.

For a successful summary, the response can include:

{
  "orderId": "ord_123",
  "result": "The document describes...",
  "usage": {
    "inputTokens": 842,
    "outputTokens": 126
  },
  "delivery": {
    "status": "delivered",
    "resultHash": "0x..."
  },
  "payment": {
    "status": "settled",
    "paymentId": "pay_123",
    "txHash": "0x..."
  }
}
{
  "orderId": "ord_123",
  "result": "The document describes...",
  "usage": {
    "inputTokens": 842,
    "outputTokens": 126
  },
  "delivery": {
    "status": "delivered",
    "resultHash": "0x..."
  },
  "payment": {
    "status": "settled",
    "paymentId": "pay_123",
    "txHash": "0x..."
  }
}
{
  "orderId": "ord_123",
  "result": "The document describes...",
  "usage": {
    "inputTokens": 842,
    "outputTokens": 126
  },
  "delivery": {
    "status": "delivered",
    "resultHash": "0x..."
  },
  "payment": {
    "status": "settled",
    "paymentId": "pay_123",
    "txHash": "0x..."
  }
}

The transaction hash may be optional depending on the receipt and privacy configuration. A signed receipt can bind the resource URL, payer, network, issue time, and payment reference without exposing more application data than necessary.

The HTTP response should also carry the protocol's settlement response where required. The application response and protocol response serve different jobs: one delivers the AI result; the other communicates payment state.

8. Record Delivery and Settlement Separately

A merchant ledger should not collapse “paid” and “delivered” into one boolean. Use separate fields:

Record

Meaning

payment_authorized

The signed payload passed local or facilitator verification

payment_settled

The payment was submitted and accepted under the scheme's settlement rules

execution_succeeded

The AI service produced the contracted result

delivery_confirmed

The result was returned or made available with evidence

refund_pending

A remedy has been initiated but not completed

refunded

The merchant recorded the refund outcome

This separation handles cases that a simple middleware integration misses:

  • payment is verified but the model times out;

  • the model returns an invalid result;

  • settlement succeeds but the HTTP response is lost;

  • a webhook arrives after the client retries;

  • the client receives a result but the merchant's settlement record is delayed.

The reconciliation worker should periodically compare the order database, facilitator status, chain evidence, inference job state, and delivery log. Any mismatch should enter a recoverable state rather than silently becoming a second charge.

9. Handle Retries, Timeouts, and Refunds

The most dangerous failure is not an obvious invalid payment. It is uncertainty after a valid payment attempt.

Payment retry

If the client receives a timeout after submitting payment, it should query the existing order or payment identifier first. It should not immediately create a new signature for the same logical request.

Execution retry

If payment is valid but inference fails transiently, retry the worker job under the same order. Keep the payment state unchanged. A second inference attempt is not automatically a second charge.

Paid but not delivered

If settlement is confirmed and no result can be produced within the service's deadline, choose and document one remedy:

  • refund the payment;

  • issue platform credit;

  • deliver a substitute result;

  • send the order to manual review.

The remedy should be recorded against the original order and payment ID. A refund is a business operation, not something that happens automatically because an HTTP request returned 500.

Duplicate request

Use a durable idempotency store in production. An in-memory map is adequate only for a single-process test. The key should bind the payer, resource, normalized input, and request intent. A malicious or accidental replay should not retrieve a paid result for a materially different request.

A Minimal Reference Architecture

Agent client
  | request
  v
Resource server / x402 middleware
  | 402 payment requirement
  | PAYMENT-SIGNATURE
  v
Payment verifier / facilitator
  | valid or invalid
  v
Order service ---- Idempotency store
  | verified job
  v
Inference worker
  | result + usage + hash
  v
Settlement + receipt service
  | payment, delivery, refund records
  v
Agent receives result
Agent client
  | request
  v
Resource server / x402 middleware
  | 402 payment requirement
  | PAYMENT-SIGNATURE
  v
Payment verifier / facilitator
  | valid or invalid
  v
Order service ---- Idempotency store
  | verified job
  v
Inference worker
  | result + usage + hash
  v
Settlement + receipt service
  | payment, delivery, refund records
  v
Agent receives result
Agent client
  | request
  v
Resource server / x402 middleware
  | 402 payment requirement
  | PAYMENT-SIGNATURE
  v
Payment verifier / facilitator
  | valid or invalid
  v
Order service ---- Idempotency store
  | verified job
  v
Inference worker
  | result + usage + hash
  v
Settlement + receipt service
  | payment, delivery, refund records
  v
Agent receives result

The payment middleware should not own all of this logic. It can enforce the route requirement and expose lifecycle hooks. The merchant application still owns product pricing, order state, model execution, result storage, delivery evidence, refunds, and reconciliation.

Where GOAT Network Can Fit

GOAT Network is relevant when the service needs a broader agent-commerce stack around x402 rather than a bare 402 middleware layer. GOAT's AgentKit documentation separates payer-side x402 actions from merchant-side operations, with merchant capabilities covering areas such as authentication, orders, balances, webhooks, and API keys. Its agent tooling also connects x402 payments with wallet actions and ERC-8004 identity capabilities.

For the reference service, that can map to three boundaries:

  • x402 handles the machine-readable payment request and payment proof;

  • AgentKit can provide wallet, payer, or merchant integration surfaces;

  • the inference application remains responsible for model execution, delivery evidence, idempotency, and refund policy.

This is a useful division of responsibility. GOAT Network does not make every inference result correct, and x402 does not guarantee service delivery. The merchant still needs to test current SDK behavior, supported networks and assets, key custody, facilitator configuration, and production availability before accepting real payments.

Production Checklist

Before enabling a pay-per-request AI service, verify:

  • the billable unit and price are unambiguous;

  • the 402 requirement names the intended route and recipient;

  • wallet policy checks amount, asset, network, expiry, and seller;

  • payment verification happens before expensive work;

  • idempotency covers both payment and inference execution;

  • model usage and output evidence are recorded;

  • payment and delivery states are separate;

  • signed offers or receipts are used when stronger evidence is required;

  • retries query existing order state before creating new payment attempts;

  • paid-but-undelivered cases have a refund or review path;

  • facilitator and webhook failures are reconciled;

  • secrets and sensitive prompts are excluded from unnecessary payment metadata;

  • testnet and small-value mainnet tests cover failure paths.

FAQ

Does a 402 response mean the AI service has been paid?

No. It means the server has declared payment requirements. The client must authorize a payment payload, and the server must verify and settle it according to the selected scheme.

Should the AI service execute before or after settlement?

The exact order depends on the x402 scheme and integration. The service should never execute before payment authorization is verified, and it should not report successful delivery before the result and payment state are recorded consistently.

How do pay-per-request services prevent double charging?

Use a durable order ID and idempotency key that bind the payer, resource, request, and payment identifier. On retry, query the existing order and return its result or status instead of creating a new charge.

What happens if payment succeeds but inference fails?

The order should move to a paid-but-undelivered state. The service can retry execution, issue a refund or credit, provide a substitute result, or route the case to review according to its published policy.

Is a facilitator the same as a merchant order system?

No. A facilitator can verify payment payloads and submit settlement transactions. The merchant still needs product pricing, order state, fulfillment, delivery records, refunds, and reconciliation.

Can GOAT Network provide the whole pay-per-request service?

GOAT Network can provide relevant AgentKit, x402, wallet, and merchant integration surfaces, but the developer still owns the AI service's business logic, model execution, delivery guarantees, and remedy policy.

The Real Product Is Verified Delivery

A pay-per-request AI service is not just an endpoint with a price. It is a stateful contract between a request, a payment authorization, a computation, a delivered result, and a recovery path.

x402 can make the payment requirement machine-readable. A facilitator can reduce verification and settlement work. AgentKit can connect wallet and merchant capabilities in a broader agent stack. None of those layers replaces the seller's responsibility to define the product, execute it once, prove delivery, and repair failures.

Build the 402 response as the first step in the transaction, then design the order, worker, receipt, reconciliation, and refund states around it. That is what turns a pay-per-request demo into a service an AI agent can use without guessing whether it has paid, received the result, or needs to try again.

[01]

AI Knowledge base

More Articles

More Articles

More Articles