Integration Guide
This is the detailed integration guide for the GOAT Flow API and SDKs. Use the Flow Quick Start for a first payment and the API Reference for field-level wire contracts.
Contents
- System boundaries
- Packages and versions
- Integration surfaces
- DIRECT order flow
- Server SDK integration
- Browser order integration
- Hosted Checkout
- QuickPay
- MPP
- Authentication
- Order lifecycle
- Errors and retries
- Production checklist
- Known compatibility notes
1. System boundaries
GOAT Flow is the product name. x402 is the payment-challenge protocol used by
the order surfaces. MPP is a separate, independent
open protocol. The GOAT Flow MPP components implement the current integration
profile. Public packages use goatflow-*; repository directories, protocol
fields, and fixed environment variables may retain goatx402.
Security boundaries:
- Merchant API key and secret exist only on the backend.
- The browser receives payment terms, never merchant credentials.
- A wallet transaction hash is not a fulfillment decision.
- Backend status/proof, an authenticated deployment-defined webhook, or the
GOAT Flow MPP profile's
Payment-Receiptextension is the authoritative completion signal for the corresponding surface.
2. Packages and versions
| Package/module | Role | Current manifest/runtime |
|---|---|---|
goatflow-sdk-server | TypeScript merchant backend | 0.3.0, Node >= 18 |
github.com/goatnetwork/goatflow-sdk-server | Go merchant backend | Go 1.25, source-only |
goatflow-sdk | Browser wallet, ERC-20, MPP | 0.2.1, ethers ^6.9.0 |
goatflow-checkout | Hosted Checkout opener | 0.1.0 |
goatflow-quickpay | Public payer/agent library and CLI | 0.3.0, Node >= 18 |
@goatnetwork/mpp-middleware | Merchant MPP middleware | 0.1.0 |
Use package manifests and exported types as the version source of truth. Do not copy version numbers into application compatibility logic.
3. Integration surfaces
3.1 Hosted Checkout
Use when the application should rely on GOAT Flow-hosted checkout software for wallet connection and transfer UX. Your backend creates dynamic sessions; fixed QuickPay products can open directly.
3.2 Custom order flow
Use when the merchant builds its own wallet and transfer UI. The backend creates
the order and maps it to the browser SDK Order; the buyer's browser submits
the transfer.
3.3 QuickPay
Use for public payer links, products, custom amounts, agents, and CLI payments. The payer discovers a same-origin manifest and does not need merchant API credentials.
3.4 MPP
Use the current GOAT Flow MPP profile for paid API routes. The buyer obtains the
profile's JSON challenge, submits a direct transfer, asks Core to verify the
transaction, and attaches the returned signed Payment-Receipt extension to
the protected request. This is not the standard MPP
Challenge/Credential/Receipt HTTP wire exchange.
4. DIRECT order flow
The DIRECT order flow is:
- Backend creates an order.
- Core returns an x402 challenge with
payTo. - Browser transfers the token to the merchant receiving address.
- Core observes the transfer and advances order state.
Flow identifier: ERC20_DIRECT.
5. Server SDK integration
5.1 TypeScript client
Create an order:
createOrder() accepts the successful HTTP 402 response and normalizes the
first x402 payment option. Use createOrderRaw() for the literal x402 object.
Operator-provisioned callback orders are outside public merchant onboarding. When a deployment contract explicitly enables one, follow the complete fields, EIP-712 signing, signature-submission endpoint, and chain-switching rules in the API Reference appendix.
Read status and proof:
Cancel only while pending:
5.2 TypeScript errors
For authenticated HTTP failures, the client parses error or message,
attaches status, optional code, and runtime responseBody, and names the
runtime-exported error GoatFlowError. instanceof GoatFlowError is supported.
Fetch failures may remain native errors.
5.3 Go client
The Go client also exposes:
CreateOrderRawCreateCheckoutSessionGetOrderStatusGetOrderProofCancelOrderGetMerchantWaitForConfirmationSetHTTPClient
Go HTTP failures are returned as *goatflow.APIError; use errors.As.
Transport and JSON failures are wrapped Go errors.
5.4 Polling and HTTP compatibility differences
| Behavior | TypeScript | Go |
|---|---|---|
| First status read | Immediate | After the first interval tick |
| Status-read error while polling | Transient errors retried; deterministic 4xx surfaced | Suppressed and retried |
| Status callback | onStatusChange | None |
| Cancellation | Overall timeout plus per-request deadline | Timeout or context.Context |
| Built-in terminal states | PAYMENT_CONFIRMED, INVOICED, FAILED, EXPIRED, CANCELLED | Same |
| First-party request deadline | 30 seconds, bounded by remaining wait timeout | 30-second default HTTP client; replaceable |
| Authenticated HTTP success | Any 2xx; 402 only for order creation | Exactly 200; 402 only for order creation |
TypeScript retries request timeouts, network failures, 408, 429, and server
errors within the overall wait deadline; other deterministic 4xx errors are
surfaced immediately. Go currently retries all status-read errors.
6. Browser order integration
6.1 Do not pass the server order unchanged
The server SDK Order has:
fromChainIdpayToChainId- no
fromAddress
The browser SDK Order requires:
chainIdfromAddress
Map a minimal object:
Avoid { ...serverOrder, fromAddress, chainId }; an explicit allowlist makes the
boundary reviewable and prevents internal/raw fields from leaking.
6.2 Validate before payment
PaymentHelper.pay() does not validate chain, payer, or expiry.
6.3 Pay
Actual pay() behavior:
- Read signer address.
- Read token balance.
- Return failure if balance is insufficient.
- Submit
transfer(payToAddress, amountWei). - Wait for a receipt.
- Require receipt status
1. - Return
txHash.
The method catches errors and normally returns { success: false, error }
instead of throwing them.
PaymentHelper.pay() treats every tx.wait() exception as failure and does not
classify TRANSACTION_REPLACED. A successful wallet speed-up can therefore
look failed. Reconcile the original/replacement hash and backend order before
considering another transfer.
6.4 ERC-20 approval helpers
Order payment itself uses transfer() and does not require an allowance.
Approval helpers are for other integration needs.
The helper:
- validates
bigintand uint256 bounds before any transaction - skips writes when the target allowance is already set
- uses a direct-write
eth_callprobe - falls back to confirmed
approve(0)only when required - follows matching fee-bump replacements
Use ERC20Token.setApproval() when the reset transaction hash is also needed.
7. Hosted Checkout
7.1 Fixed product
7.2 Dynamic DIRECT session
The SDK serializes nested values into signed JSON strings.
The response exposes checkoutType as string; handle unknown future values
explicitly. Public integrations create DIRECT sessions. Operator-provisioned
compatibility fields, deprecated wrappers, signature submission, and the
callback_template trust boundary are documented in the
API Reference appendix.
7.3 Hosted security model
originmust be a bare HTTPS origin; HTTP is allowed only for loopback.- The opener accepts messages only from the configured origin, exact popup source, and matching nonce.
- A server-created checkout ID is an opaque bearer capability.
- Product and checkout-session prices are server-authoritative.
- Browser callbacks are non-sensitive UX events, not proof of payment.
8. QuickPay
The canonical public link shape is:
The package:
- Validates the URL scheme and canonical path.
- Takes the merchant ID from the trusted URL path.
- Derives the same-origin manifest URL.
- Validates the manifest merchant ID and rail data.
- Derives session/MPP endpoints from the same origin.
The raw session API uses snake_case:
QuickPayClient methods do not accept that object. payX402() uses
amount, chainId, tokenSymbol/tokenContract, memo, and
idempotencyKey; payProduct() uses productKey, chain/token selection, and
optional idempotencyKey. The client derives merchant_id from the trusted URL
and payer_addr from the payment backend. There is currently no
clientReferenceId library option.
CLI:
Product mode uses the manifest's decimal price and the chosen token decimals.
Custom amount mode is untrusted for automatic fulfillment unless the backend
reconciles the actual paid amount.
QuickPay session terminal states are PAYMENT_CONFIRMED, EXPIRED, FAILED,
and CANCELLED; they are separate from Server SDK order states. Status polling
uses pollTimeoutMs as a hard cap, retains known transaction hashes across
transient failures, and performs five bounded grace polls when a known
transaction is reported EXPIRED. Reconcile by session ID and transaction hash
instead of rebroadcasting after an ambiguous failure.
9. MPP
MPP itself is an independent, payment-method-agnostic open protocol. This
section documents the current GOAT Flow adapter, not a generic or official
MPP SDK. Its /mpp/v1/challenge and /mpp/v1/verify JSON endpoints and signed
three-segment receipt are deployment-specific contracts.
9.1 High-level flow
requestCanonical defaults to routeCanonical. When present, it must preserve
the route prefix accepted by Core.
For the standalone GOAT Flow MPPClient adapter, coreUrl is the configured
Core/API origin for the deployment. The QuickPay pay-mpp adapter instead sets
coreUrl to the trusted
QuickPay link origin so manifest, challenge, and verify stay same-origin. Do not
assume those two deployment origins are universally the same.
9.2 Challenge
The GOAT Flow profile's POST /mpp/v1/challenge returns HTTP 402 on success.
The client decodes:
challenge_idexpiry(and legacyexpiry_unix)amount_weichain_idtoken_contractrecipientmacroute_pricing_version
These challenge fields are authoritative for the payment. Route data in a manifest is discovery metadata; after challenge issuance, do not override the amount, chain, token, recipient, expiry, MAC, or pricing version.
9.3 Broadcast
Before broadcasting, payChallenge() checks:
- signer has a provider
- challenge has not expired
- provider chain matches challenge chain
It submits the buyer-authorized ERC-20 transfer and returns { txHash, tx }
immediately. Pass txHash to verifyChallenge(); use the ethers
TransactionResponse in tx to observe a matching fee-bump replacement. The
method does not wait for a local receipt because
/mpp/v1/verify confirms on-chain finality for this integration profile and
issues its signed receipt. It does not hold or release the transferred tokens.
9.4 Verify and retry
POST /mpp/v1/verify behavior:
| Response | Behavior |
|---|---|
200 + Payment-Receipt | Decode and return receipt |
202 | Retry using Retry-After |
429 | Retry using Retry-After |
other 4xx | Terminal MPPError |
5xx | Bounded exponential backoff |
| network rejection | Bounded exponential backoff |
Default maximum attempts: 16. Maximum per-wait delay: 30 seconds.
The transfer replacement watcher follows only a replacement with the same destination and calldata. A user cancellation or unrelated same-nonce transaction is not treated as the challenged transfer.
9.5 Recovery after broadcast
Once a transfer has been broadcast, do not call pay() again merely because
verification failed. Preserve and resume the challenge/transaction context.
Browser Core CORS must allow the DApp origin and expose the Payment-Receipt
response header. The protected resource must allow that origin and the
Payment-Receipt request header.
10. Authentication
The server SDKs produce:
The signed parameter set contains:
- every non-null top-level body field converted to string
api_keytimestampnonce
Keys are sorted and joined as key=value&...; empty strings and sign are
excluded.
Hosted Checkout nested fields are JSON-stringified because this signing scheme does not canonicalize nested JSON.
Operational rules:
- Generate a new nonce for every request.
- Keep system time synchronized.
- Never log the API secret or full credential set.
- Avoid custom signing unless cross-validated against both SDK tests.
11. Order lifecycle
Current TypeScript status values include:
Recommended application classification:
| Class | Status |
|---|---|
| Pending | CHECKOUT_VERIFIED |
| Confirmed success | PAYMENT_CONFIRMED, INVOICED |
| Failure/closed | FAILED, EXPIRED, CANCELLED |
Server SDK order waiters treat INVOICED as a successful terminal state. Core
can advance a DIRECT order from PAYMENT_CONFIRMED to INVOICED inside one
watcher transaction, so polling code must not require observing both states.
Do not assume every deployment exposes each transition.
cancelOrder() is documented for CHECKOUT_VERIFIED. Reservation restoration,
fee refunds, and automatic-expiration effects are not part of the public SDK
contract; confirm them with the active API before relying on them.
12. Errors and retries
12.1 Merchant API
| HTTP | Treatment |
|---|---|
400 | Validation/business error; inspect body |
401 | HMAC/timestamp/nonce failure |
402 | Success only for documented challenge endpoints |
403 | Ownership/authorization failure |
404 | Resource not found |
5xx | Caller-managed bounded retry for idempotent operations |
Do not blindly retry order creation without a stable merchant
dappOrderId/idempotency strategy.
Both server SDKs fail closed on unexpected authenticated 402 responses.
Individual merchant API calls do not retry automatically.
waitForConfirmation() retries eligible status-read failures within its
overall deadline; Go WaitForConfirmation currently retries all status-read
errors. This differs from the MPP verifier, which has its own bounded retry
policy.
12.2 Browser order payment
PaymentHelper.pay() returns payment failures:
Wallet user rejection, RPC errors, transfer reverts, and failed receipts are
converted to the error string.
12.3 MPP
MPP methods throw MPPError. Branch on code, not message. Important codes
include:
network_errorparse_errorbad_requestinvalid_requestroute_not_foundchain_mismatchuser_rejectedpayment_failedchallenge_expiredchallenge_already_consumedchallenge_tx_hash_mismatchpayer_mismatchverify_timeoutservice_unavailablereceipt_missingreceipt_malformed
Application onPhase callbacks can throw outside the SDK's error wrapper and
replace the expected MPPError, including during the failed phase. Keep
phase callbacks non-throwing or catch their errors locally.
13. Production checklist
- Keep merchant credentials in a backend secret store.
- Use separate credentials and origins per environment.
- Derive amount/token/chain from server-side product/cart data.
- Map server orders to browser orders with an explicit allowlist.
- Validate chain, payer, and expiry before
PaymentHelper.pay(). - Treat authenticated
PAYMENT_CONFIRMEDandINVOICEDorder states as successful terminals, while validating all expected order fields. - Fulfill idempotently from trusted server evidence.
- Cancel abandoned
CHECKOUT_VERIFIEDorders. - Monitor merchant fee-balance errors.
- Treat checkout IDs and QuickPay session IDs as capabilities.
- For the browser MPP adapter, allow the DApp origin on Core and the protected resource,
expose the response
Payment-Receipt, allow that request header, and retain recoverable verify context after broadcast.
14. Known compatibility notes
14.1 Polling differences
Both polling helpers stop on PAYMENT_CONFIRMED, INVOICED, FAILED,
EXPIRED, or CANCELLED. TypeScript performs the first read immediately and
selectively retries transient failures; Go waits one interval and retries every
status-read error until timeout/context cancellation. Use explicit polling when
an application needs one cross-language retry policy.
14.2 Merchant token-list field
The TypeScript getMerchant() implementation reads wallets[] and maps it to
supportedTokens. The Go MerchantInfo type expects supported_tokens.
Verify the target deployment response before using the Go field.
14.3 Browser compatibility
No tested minimum-version browser matrix is currently published. The browser path
requires an EIP-1193 wallet/provider and modern features used by ethers and the
SDK (BigInt, fetch, URL, Promises, and Web Crypto/browser primitives).
14.4 Runtime capability
Supported chains, tokens, fee configuration, redirect allowlists, and merchant payment capabilities are deployment-specific. Discover them from trusted runtime responses or operator configuration rather than a static documentation table.
14.5 Generated declaration examples
The current generated Checkout declaration contains an outdated example
origin: goatx402-checkout/dist/types.d.ts mentions pay.goat.network. It is
not an active deployment origin and must not be copied into integrations. The
current origins are listed in the documentation hub.
14.6 MPP interoperability
The current MPPClient and middleware do not implement the standard MPP
HTTP Challenge/Credential/Receipt exchange. They use GOAT Flow JSON
challenge/verify endpoints, a direct ERC-20 transfer, and a signed
three-segment receipt extension. No conformance or interoperability result with
official MPP SDKs is currently published. Treat these components as a
deployment-specific adapter unless standards compatibility is explicitly
documented and tested.