API Payments
API Payments lets an application or agent pay for an API operation. Choose a Custom Integration when your server owns the order and delivery lifecycle, or MPP when a programmatic client must present a route-bound payment receipt on the protected request.
Choose an Integration
| Path | Use it when | Completion signal |
|---|---|---|
| Custom Integration | Your API owns authentication, pricing, order creation, and delivery | Verified order status or authenticated webhook |
| MPP Integration | A client or agent must retry the protected request with proof of payment | Verified Payment-Receipt |
Custom Integration
Request Flow
Install the Server SDK:
The example below is a Crypto payment for a public DIRECT merchant. Keep
pricing and merchant credentials on the server. Validate the payer and select
the chain, configured token contract, and amount from trusted server-side
configuration; 1000000 is 1 USDC only for a configured six-decimal USDC token.
The success predicate in this Crypto example is not a generic Card fulfillment
rule: Card can be held at PAYMENT_CONFIRMED. Card integrations must use the
platform completion gates
rather than copying this Crypto-only condition.
Associate the Business Request and Payment
Use a stable application request ID for one report purchase. This is a key
in your own database, not an idempotencyKey parameter accepted by the Server
SDK. Expose the following application functions through your authenticated
POST /api/reports and GET /api/reports/{reportId} handlers. Never accept
customerId from an unauthenticated request body.
The ReportStore interface below is an application-owned persistence contract,
not a GOAT Flow SDK API. Implement it with your own database:
reserveAndClaim()must atomically create or load one record per(customerId, requestId). Persist the input, price, payment terms, and a stabledappOrderIdsuch asreport:<reportId>before contacting GOAT Flow. Reject reuse of the request ID with different inputs or payment terms.- Only the first caller receives
mayCreateOrder: true. Concurrent requests, process restarts, and timeouts must not automatically release this claim or grant it again. Existing requests return the saved payment requirements or a pending/reconciliation state instead of creating another order. recordOrder()durably saves the raw response, includingpayment.order_id, againstreportId.getOwned()must enforce customer ownership.deliverOnce()must durably coordinate report generation and return the saved result on retries. Polling, webhooks, and concurrent requests must not generate or deliver the same purchase more than once.
The following functions import the Server SDK setup above. The repository
must construct job.orderParams from the persisted server-approved terms and
validated payer, not from arbitrary fields supplied by the client.
Your HTTP handlers translate httpStatus into the response status and return
the remaining fields as the body. The PAYMENT_SETUP_PENDING and
RECONCILIATION_REQUIRED values above are your application's states, not
GOAT Flow order statuses. Both prevent a second create attempt until the
original outcome is known. If even the local error-state write fails, the
original durable creation claim must still block another attempt.
Persist this association:
waitForConfirmation() resolves on PAYMENT_CONFIRMED, INVOICED, FAILED,
EXPIRED, or CANCELLED; it does not throw merely because the returned state
is a failure. The explicit success check above is required before delivery.
Failure terminal states must not be treated as a still-pending payment or a
reason to charge again automatically. The example's 409 is your merchant API's
chosen response for that outcome, not an exception or HTTP status raised by
the polling helper.
The helper throws on its overall timeout or a non-retryable request error, such as 401 or 404. Handle those exceptions in your HTTP error handler or background worker: keep the saved mapping, inspect the current order status, and do not issue another payment merely because polling stopped. For long-running work, return the report ID and use a worker or authenticated webhook to invoke the same durable, idempotent fulfillment operation. See SDK polling behavior.
Idempotency and Recovery
- Reuse the same application request ID for a retry of one logical purchase; reject changes to its saved inputs. Use a new ID only for a genuinely new purchase, not as a way around an unresolved payment.
- Server SDK
0.4.0does not accept or sendidempotencyKeyoncreateOrder()orcreateOrderRaw(). Ordinary duplicatedappOrderIdrequests are rejected asorder already exists; they do not return the existing order. Do not use duplicate creation as an order lookup. - If the GOAT Flow order ID is known, reuse the saved requirements while the order remains payable and query that order's status/proof. Do not replay expired requirements or automatically send another transfer.
- If creation timed out, its response was lost, or saving the returned ID
failed, keep the business record blocked for reconciliation. Use the saved
merchant,
dappOrderId, payer, chain, token, amount, and attempt time to locate the original order in the Merchant Portal or with platform support. Do not assume there is a public lookup-by-dappOrderIdSDK method. - Save any broadcast transaction hash before verification/polling. A timeout, duplicate-order error, or expired payment window is not proof that no funds moved. Reconcile the original order and transaction before authorizing a replacement payment.
- Fulfillment must be idempotent: polling and authenticated webhook delivery can race, and a response can be lost after delivery has completed.
MPP Integration
This section describes the GOAT Flow MPP profile for payment-gated HTTP
routes. The /mpp/v1/challenge and /mpp/v1/verify endpoints, JSON fields, and
three-part Payment-Receipt format below are GOAT Flow's concrete interface
contract. Do not assume an arbitrary MPP SDK or receipt verifier is directly
compatible; use a deployment-matched GOAT Flow client and verifier.
GOAT Flow MPP Crypto Payment Flow
This example shows the successful Crypto prepayment path. A client requests a
challenge from GOAT Flow, sends the quoted on-chain payment to the configured
merchant recipient, and submits the transaction for verification. After GOAT
Flow verifies the payment and issues a signed Payment-Receipt, the client
requests the protected resource with that receipt. The merchant API verifies
the receipt and applies its own access checks before returning the resource.
The full verification request includes challenge_id, payer_addr, tx_hash,
and mac; see the Buyer Flow. If verification returns
202 pending_inclusion or pending_finality, honor Retry-After and keep
verifying the original challenge and transaction. Do not send another payment
while waiting for confirmation; see Retry Rules.
Before You Integrate
Adding middleware to your API does not register or enable a paid route in GOAT Flow. Before requesting a challenge:
- Confirm with the platform operator that MPP is enabled in your target deployment and that your active merchant account has MPP enabled.
- Register and enable the intended
route_canonicalthrough the Merchant Portal or the platform operator. Merchant owners can manage their routes after merchant-level MPP access has been enabled by an operator. - Configure the route's supported chain, registered Token, receiving address, and amount. The chain/Token must be allowed by the deployment and the receiving address must belong to the merchant's configured addresses.
- Use the same merchant and route identifiers in route configuration, challenge requests, and middleware configuration. Obtain the deployment's trusted Ed25519 receipt-verification public key and prepare the shared receipt store before starting your API.
GET:api:data is a registered route identifier, not a literal URL. Pricing is
route-scoped; use server-configured routes for distinct prices. Do not let
clients choose an arbitrary price or receiving address in a challenge request.
When the MPP runtime is disabled, /mpp/v1/* is not mounted. When it is enabled
but there is no eligible registered route, challenge creation returns
404 route_not_found. Middleware alone does not resolve either condition.
Obtain the TypeScript Middleware
Do not assume @goatnetwork/mpp-middleware is installable from public npm: the
public registry did not expose it when this example was verified. The public
source package is marked private, while the target mainnet source configures
version 1.0.0 for public publishing; a publish configuration is not proof of
an available release.
Obtain an approved, deployment-matched source archive or built package from the platform team. If building from the approved source tree, use its lockfile:
Install the resulting archive in your merchant API project, rather than assuming a registry installation succeeds:
The following ESM TypeScript example targets that approved 1.0.0 middleware
build with Express 4 and Node.js 22 or later. Save it as server.mts and run it
with npx tsx server.mts after configuring the application variables below.
These are settings for your merchant API, not additional GOAT Flow service
endpoints:
| Application setting | Value to provide |
|---|---|
MPP_MERCHANT_ID | Your merchant identifier in the target deployment |
MPP_PUBLIC_KEY_HEX | The trusted platform receipt-signing public key, encoded as 64 hexadecimal characters (32 decoded bytes) |
REDIS_URL | Your shared receipt-consumption Redis connection URL |
Get the public key through the platform operator's trusted distribution channel, load it at startup, and coordinate rotation with the operator. Never accept a verification key from the buyer or substitute an API secret or merchant audience identifier. No automatic public-key discovery URL is assumed by this example.
Protect a TypeScript Route
This route is a verification demonstration, not an implementation of customer
authentication or report delivery. Use the configured merchant ID in the buyer
requests below in place of merchant_123. expectedLivemode: true rejects
test-mode Card receipts; use false in your separate test deployment. It has
no effect on Crypto-only receipts.
The Redis operation atomically consumes the middleware-provided deduplication
key and preserves its TTL across all API instances. Do not replace that key
with just a transaction hash or discard a lineage: prefix. A shared store is
required for single-use authorization across replicas, and for receipts with
a purchase lineage on either payment rail. Configure Redis persistence and
retention so consumption records cannot silently disappear before expiry.
In-memory stores are for local tests, not restart-safe production recovery.
This simple ReceiptIDStore consumes before your handler runs. A handler
failure or lost response therefore does not automatically make the receipt
reusable. Persist delivery results and provide an authenticated result-retrieval
path, or implement the middleware's TwoPhaseReceiptIDStore contract with
durable reserve/commit/release semantics. Do not delete a consumed key or ask
the buyer to pay again merely because delivery could not be observed.
Buyer Flow
Request a challenge using the merchant and registered route from your setup:
Persist the returned challenge ID, MAC, GOAT Flow order ID, quoted payment terms, and expiry. After submitting the exact transfer, save the transaction hash and verify it with the original challenge credential:
A successful response returns a Payment-Receipt. Present that receipt on the
protected request:
Receipt Verification
Middleware verifies the receipt format and signature, merchant audience, route binding, expiry, the configured live/test mode for Card receipts, and receipt consumption when a store is configured. Lineage-bearing receipts require a store. Use Ed25519 for multi-tenant production deployments. A shared HMAC secret is appropriate only when all verifiers are within one trusted tenant.
Route matching is an exact match or a :-delimited prefix match against the
configured routeCanonical. It is not an automatic comparison of the
current HTTP request's user, URL, query parameters, or body with the paid
operation. The middleware does not authenticate the current customer or prove
that a submitted payer address belongs to that customer.
Keep application authentication and authorization in place. If parameters,
customer identity, selected data, or price change what can be delivered,
maintain a server-side purchase record and explicitly check the verified
receipt against that record and the current request. A valid receipt for a
route must not unlock every customer's data or every operation on that route.
Perform authentication and request/ownership validation before the payment
middleware, and check receipt-dependent business conditions before delivering
protected work. The simple store above has already consumed the receipt by
then. If consumption must depend on those later checks, integrate verification
and reservation with your application's authorization flow rather than assuming
the stock adapter will undo consumption. Even the two-phase framework adapter
commits non-5xx responses, including business rejections such as 403.
Retry Rules
| Result | Action |
|---|---|
200 or already_processed | Use the returned receipt for the same purchase; do not send another payment |
202 pending_inclusion / pending_finality | Honor Retry-After; retry verification with the original challenge and transaction evidence, without another transfer |
400 tx_rejected | Inspect the submitted transaction and order requirements: the cause may be a revert, insufficient amount, wrong recipient/Token, or other mismatch. Reconcile before correcting the evidence or authorizing a replacement payment |
401 challenge_expired | Stop automatic payment. First reconcile the original challenge, order, and any broadcast transaction; expiry does not prove the purchase is unpaid |
401 challenge_not_issued / challenge_receipt_expired / challenge_already_consumed | Preserve the original evidence and reconcile payment and delivery; do not automatically start another charge |
409 tx_already_claimed | Find the order already associated with that transaction and reconcile it before taking another payment |
429 / 503 | Honor Retry-After; preserve the challenge and transaction state and retry the applicable verification operation, not the transfer |
Before the first transfer, an expired, unpaid challenge can be replaced. Once a transaction has been broadcast or its outcome is uncertain, do not run a new payment loop just to obtain a fresh challenge.
The backend has separate recovery windows: a pending challenge can remain verifiable during its configured grace period, and an already-successful challenge can return its cached receipt while that receipt remains valid. Retry the original credential within the applicable window. These windows are bounded: Crypto does not guarantee receipt recovery after the challenge has been evicted from its replay store. If verification can no longer recover a receipt but the payment may have succeeded, reconcile with the platform operator rather than paying again automatically.
A consumed receipt or a lost merchant response is a delivery-recovery case,
not a new purchase. Recover the saved result or the application fulfillment
record instead of treating receipt_already_consumed as a reason to pay again.
Trust Boundary
| Component | Responsibility |
|---|---|
| Your API | Client authentication, pricing, request validation, delivery, refunds, and support |
| GOAT Flow | Payment requirements, order lifecycle, matching, status, webhooks, and receipt verification services |
| Network | Transaction execution and finality |
| Client or agent | Reviewing the requirement and authorizing payment |
Continue with SDK Reference or API Reference.