RailbedDocs

Payments

A payment is what a checkout session became. Read it to fulfil orders, list payments to reconcile, and simulate outcomes in Test mode.

The payment object

A payment has every field of its checkout session, with object set to "payment", plus:

FieldTypeDescription
paid_atinteger or nullWhen it became paid, in Unix seconds
providerstring or nullThe provider the buyer chose, such as stripe or paypal
hold_reasonstring or nullWhy it's held, in plain words. Null otherwise
hold_acceptablebooleanFor a held payment: whether you can accept it as paid in the dashboard
canceled_atinteger or nullWhen you canceled it (payment links only). A canceled payment's status is expired
settlementobjectWhat arrived and where it went. See below

The settlement object:

FieldTypeDescription
coinstring or nullWhat was delivered, usually polygon_usdc (polygon_usdt is also possible)
value_coinstring or nullHow much arrived at the payment's deposit address, before fees
merchant_receivedstring or nullHow much was forwarded to your wallet, in the coin (six decimal places). Can be null for a while after paid and fill in later. Stays null for a held payment you accepted as paid
txid_instring or nullThe Polygon transaction that delivered the money
txid_outstring or nullThe Polygon transaction that forwarded it to your wallet
payout_walletstring or nullThe wallet it went to, fixed when the buyer started

amount and currency are always the price you set; value_coin and merchant_received are what actually moved. Look up both transactions on a Polygon block explorer to see them for yourself.

Retrieve a payment

GET/v1/payments/:id

Returns the current state of a payment of yours in the key's mode. This is the authority for fulfilment: when it says paid, the money arrived and passed Railbed's settlement checks.

curl https://pay.railbed.io/v1/payments/pay_7AAiYH0Ykt11ED4hmfiN \
  -H "Authorization: Bearer $RAILBED_SECRET_KEY"
Response
{
  "id": "pay_7AAiYH0Ykt11ED4hmfiN",
  "object": "payment",
  "url": "https://pay.railbed.io/p/pay_7AAiYH0Ykt11ED4hmfiN",
  "status": "paid",
  "livemode": true,
  "amount": "49.00",
  "currency": "USD",
  "description": "Pro Membership",
  "reference": "order_1042",
  "customer_email": "buyer@example.com",
  "created": 1790380525,
  "expires_at": 1790466925,
  "started_at": 1790380611,
  "metadata": { "user_id": "player_1042" },
  "paid_at": 1790381342,
  "provider": "stripe",
  "hold_reason": null,
  "hold_acceptable": false,
  "canceled_at": null,
  "settlement": {
    "coin": "polygon_usdc",
    "value_coin": "47.53",
    "merchant_received": "46.341750",
    "txid_in": "0x0c651ba1d59c7a32e8b1f4bd2c7e0e4f96a55d13a6b0f2d1c8e7a9b4f3d29b58",
    "txid_out": "0x8202d1373e0a9c4f1b6d5e2c7a8f9b0e1d2c3b4a5f6e7d8c9b0a1f2e3d7356ff",
    "payout_wallet": "0xF977814e90dA44bFA03b6295A0616a897441aceC"
  }
}
200 OK

A held payment looks like this in part:

A held payment (trimmed)
{
  "id": "pay_Kx81mQv2PzR0dT7eWcYa",
  "object": "payment",
  "status": "held",
  "amount": "49.00",
  "currency": "USD",
  "paid_at": null,
  "hold_reason": "The provider sent 24.50 USDC, below 90% of the order’s 49.00 USD value.",
  "hold_acceptable": true,
  "settlement": { "coin": "polygon_usdc", "value_coin": "24.50", "merchant_received": null }
}
200 OK

Reading a payment past its expires_at marks an unpaid one expired.

List payments

GET/v1/payments

Returns your payments in the key's mode, newest first. Use it to reconcile, not to find new payments quickly: it lists by creation time, so recheck unfinished payments you saved by id.

ParameterTypeDescription
limitinteger1–100. Default 20
starting_afterstringA payment id from the previous page's next_cursor
curl "https://pay.railbed.io/v1/payments?limit=50" \
  -H "Authorization: Bearer $RAILBED_SECRET_KEY"
Response
{
  "data": [
    {
      "id": "pay_7AAiYH0Ykt11ED4hmfiN",
      "object": "payment",
      "status": "paid",
      "amount": "49.00",
      "currency": "USD"
    },
    {
      "id": "pay_Kx81mQv2PzR0dT7eWcYa",
      "object": "payment",
      "status": "held",
      "amount": "49.00",
      "currency": "USD"
    }
  ],
  "has_more": true,
  "next_cursor": "pay_Kx81mQv2PzR0dT7eWcYa"
}
200 OK

Each item is a full payment object (trimmed here). An invalid limit is 400 invalid_limit; a cursor that isn't one of your payments in this mode is 400 invalid_cursor.

Simulate a payment

POST/v1/payments/:id/simulate

Test mode only. Decides how a started test payment ends, as a buyer on the test provider page would. The outcome goes through the same settlement checks and sends the same webhooks as a live payment.

FieldTypeDescription
outcomestring Requiredpaid (about 97% of the value arrives), underpaid (half arrives, so it's held) or failed (declined)
curl -X POST \
  https://pay.railbed.io/v1/payments/pay_7AAiYH0Ykt11ED4hmfiN/simulate \
  -H "Authorization: Bearer $RAILBED_SECRET_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "outcome": "paid" }'

The response is the payment after the outcome. A payment can end only once: simulating a finished payment returns it as it is. Use a new session for each scenario.

StatusCodeWhen
400invalid_outcomeoutcome is missing or not one of the three
403live_paymentThe payment is a Live payment
409not_startedStart the session first, with Start a checkout session

Updated · This page as Markdown