StableOps
Guides

Refund integration

Create full or partial StableOps refunds, confirm customer addresses, broadcast from your wallet, register transaction hashes, and reconcile failures safely.

StableOps records refund requests and verifies on-chain results. It does not hold merchant keys, sign transactions, or broadcast funds. Refunds are separate records; the original Payment Order stays finalized rather than changing to a refunded payment state.

Eligibility and refundable balance

  • Refund only finalized orders with an original payment event.
  • The chain, asset, and source address come from the original payment. Send from the original receiving address, not another treasury wallet, chain, or token.
  • Full and partial refunds are supported. amount is a human-readable decimal string, within the token’s decimal precision.
  • Remaining capacity is the original order amount minus refunds in requested, submitted, or confirmed. failed and canceled refunds do not reserve capacity; resubmitting a failed refund checks the balance again.

Underpayments, overpayments, wrong-chain or wrong-token transfers, and late deposits that did not finalize the order are not eligible through that order. Follow mismatched payment handling and manage actual funds through your own treasury tools.

1. Verify the payment and destination

Retrieve the original order and linked transaction. Confirm the business order, actual receiving chain, asset, and amount. Ask the customer to explicitly approve the refund chain and destination address, and link the request to your internal support or approval record.

Do not default to the original sender address. It may belong to an exchange hot wallet, custodial service, or contract, and a return transfer may not credit the customer. Ensure you control the original receiving address and have the chain’s required transaction fees or resources.

2. Create the refund request

From your backend, use the API key for the order’s organization and environment to create a non-custodial refund request:

POST /v1/refunds
Authorization: Bearer sk_sandbox_...
Content-Type: application/json

{
  "payment_order_id": "replace-with-finalized-order-id",
  "amount": "5.00",
  "destination_address": "replace-with-approved-address-valid-for-the-original-chain"
}

Save the returned id, chain, asset, amount, source_address, and destination_address. The initial requested state reserves refund capacity but does not transfer funds.

Creation is not automatically deduplicated by your business request ID. One order can have several partial refunds, and repeated creation may reserve additional capacity. Serialize handling of each internal refund request and persist its refund ID. After a timeout, list refunds and reconcile existing records before creating another request.

3. Sign and broadcast from the merchant wallet

An authorized operator or controlled wallet flow must sign and broadcast the exact transfer specified by the refund: source, destination, chain, asset, and amount. Never send private keys to StableOps or write them to application logs.

Save the transaction hash immediately after broadcast. If the wallet or network times out, check whether the transaction was already sent before trying again. Broadcasting funds and registering evidence with StableOps are separate operations; your workflow must recover after either step is interrupted.

4. Register the transaction hash

Call register refund transaction:

POST /v1/refunds/{id}/submit
Authorization: Bearer sk_sandbox_...
Content-Type: application/json

{
  "tx_hash": "replace-with-broadcast-refund-transaction-hash"
}

The state becomes submitted. While the same refund remains submitted, registering the same hash again returns the existing record without sending funds. This does not make wallet broadcasting idempotent. A transaction hash already registered to another refund cannot be reused.

5. Retrieve and confirm the result

Retrieve the refund to inspect its state:

StateMeaning and action
requestedCreated without a registered transaction; cancel only after verifying nothing was broadcast
submittedTransaction registered and awaiting verification; cannot be canceled
confirmedConfigured final confirmation depth reached, with matching source, destination, asset, and exact amount
failedFailed receipt or no expected transfer; investigate failure_reason and the actual transaction
canceledAn unsubmitted request was canceled and its capacity released

submitted does not mean the refund completed. A temporarily missing receipt or unavailable result can leave it waiting; do not send another transfer just because it is pending. Refund confirmed uses the platform’s confirmation depth, not a promise of protocol-level finality or zero risk.

The platform emits refund.requested, refund.submitted, refund.confirmed, and refund.failed. If these events drive your workflow, follow the Webhook guide for verification, deduplication, and current-state retrieval. Do not rely on a cancellation event; use the cancellation response or retrieve the record.

Cancellation and failure recovery

Cancel a request only while it is requested. If the wallet already broadcast a transaction but its hash is not registered, register it first. Do not cancel and refund again just because the platform still shows requested.

A failed refund can accept another transaction hash, but first investigate the failure and where the funds actually went. Determine whether a new transaction is needed. Resubmission rechecks capacity; other refunds created in the meantime may leave insufficient balance. A platform verification failure does not necessarily mean no funds moved on-chain.

Keep approval records, refund IDs, broadcast transaction hashes, and processing outcomes. Review long-running requested and submitted refunds. Update your ledger and customer notifications after confirmation without rewriting the original order’s terminal payment state.

How is this guide?

Last updated

On this page