Skip to main content
Run this on staging before you write integration code. It ends with a payment you confirmed by reading the resource, not by trusting a response code. You need an app ID and an app secret for one environment, and both stay on a server.
Staging is https://staging-api-v2.stableyard.fi and production is https://prod-api.stableyard.fi. A credential authenticates against one of them and returns 401 against the other. See Environments.

1. Read your configuration

This is the first call in every environment. It proves the credential and tells you what this app can actually do.
A network can settle and still have no collection path, so take the chain and asset from this response rather than from any table. Details are on Capabilities. Before moving on: status is active, capabilities.payments.available is true, and you have written down one chainId whose paymentCollection.supported is true, with an asset symbol on it. Steps 2 and 3 both use that pair.

2. Create an account

Every movement is addressed to an account. Create one for the party the money is for.
A 201 carries four resources created together:
Use the chainId from step 1 and an address from that chain’s family. subjectType is required, never inferred, and cannot be changed later. Passing wallets is what gives the account somewhere for value to land. Before moving on: store account.id, and confirm settlementProfile.settlementDestinationId is present. Without a settlement destination, step 3 is refused with settlement_profile_required.

3. Create a receive payment

A receive payment is one bounded obligation: one amount, one asset, one expiry, one reference of yours.
Use the chain and asset from step 1. expiresInSeconds accepts 60 to 86400 and defaults to 600.
A 200 here means the obligation exists, not that money arrived.
checkout.clientSecret is returned at creation and never again. Keep it out of URLs, logs and analytics. checkout.paymentUrl is a capability for this one payment: whoever holds it can view and fund it, and can reach nothing else.
Before moving on: persist payment.id against your own order_1042, with a unique constraint on that reference so one obligation can be fulfilled once.

4. Fund it

Open checkout.paymentUrl in a browser and pay it. The page works out the ways that payment can be funded; your code calls none of that. Building your own checkout instead? Put payment.id in the path and send checkout.clientSecret only as a bearer header, never in a URL:
That secret reaches this one payment and nothing else. See Authentication and Build your own checkout. Staging runs on test networks, so fund from a test wallet on the chain you chose in step 1. Before moving on: re-read the payment once. It should have left requires_payment_method.

5. Read it to a terminal state

Read two fields, not one. You are done when status is terminal and operationalState is normal. Every state, including the ones that need a person, is on Status codes. Polling got you through this once. It is not the integration.

Next: Wire webhooks

Get told when state changes, then read the payment.