> ## Documentation Index
> Fetch the complete documentation index at: https://ramps-kph-agent-spec-amendments.mintlify.site/llms.txt
> Use this file to discover all available pages before exploring further.

# Changelog

> New features, improvements, and updates to the Grid API.

Follow along with the latest additions and improvements to the Grid API. For upcoming
changes and roadmap, [contact our team](https://www.lightspark.com/contact).

<Update label="October 2026">
  ## Set the ACH statement description with `statementDescriptor`

  `POST /quotes` takes a new optional `statementDescriptor`: up to 10 characters that
  label the payment on the counterparty's bank statement, such as `PAYROLL`. It is used
  only on ACH, where it is sent as the NACHA Company Entry Description, for both a
  payout to an external US bank account and a pull from one:

  ```json theme={null}
  {
    "source": { "sourceType": "ACCOUNT", "accountId": "InternalAccount:e85dcbd6-dced-4ec4-b756-3c3a9ea3d965" },
    "destination": { "destinationType": "ACCOUNT", "accountId": "ExternalAccount:a12dcbd6-dced-4ec4-b756-3c3a9ea3d123", "paymentRail": "ACH" },
    "lockedCurrencySide": "SENDING",
    "lockedCurrencyAmount": 10000,
    "statementDescriptor": "PAYROLL",
    "remittanceInformation": "INV-12345"
  }
  ```

  Grid uses its default when you omit it. Sending it on a quote that moves no ACH entry
  returns `400 INVALID_INPUT`. Longer payment details still belong in
  `remittanceInformation`, which ACH carries in the addenda record. ACH `railDetails` on
  transactions and webhooks reports the value as `statementDescriptor`.

  The `remittanceInformation` description now also covers rails beyond ACH, RTP, FedNow,
  and wire. On rails such as SEPA, PIX, SPEI, and Faster Payments it is sent as the payment
  reference on the recipient's bank statement, which is often much shorter than 80
  characters, so put the most important information first.

  ## List balance changes for periodic statements

  `GET /internal-accounts/{id}/balance-changes` returns every change to an internal account's
  balance between `startDate` and `endDate`, in the order the money moved. The window is
  half-open, so consecutive windows leave no gap and do not overlap. The response also
  carries the window's `openingBalance` and `closingBalance`, and the opening balance plus
  the sum of every `amount` across all pages equals the closing balance.

  Each change has an `id`, `amount`, `effectiveAt`, and an optional `transactionId`
  that you can fetch from `GET /transactions/{transactionId}`. Fees are already inside
  `amount`; a transaction's total fee is `fees` on the transaction. A window whose card settlement has not
  closed returns `409 NOT_YET_AVAILABLE`. Retry once it has settled. To build a statement
  from these changes, see the guides for [cards](/cards/statements) and
  [payouts](/payouts-and-b2b/payment-flow/statements).

  ## Business customers can omit `taxId` and `incorporatedOn`

  `businessInfo.taxId` and `businessInfo.incorporatedOn` are no longer required in the
  schema when you create a business customer. They are still required when the customer's
  currency uses direct customer-owned accounts, and Grid returns `400 INVALID_INPUT` naming
  the missing field. When you fund customers from an omnibus FBO account, you can omit them.
  For details, see [configuring customers](/ramps/onboarding/configuring-customers).

  ## API tokens can expire

  `ApiToken` has a new optional `expiresAt`. When it is present, requests made with that
  token after that time return `401`, and you need to create a new token. Tokens without
  `expiresAt` do not expire.

  ## A UMA address destination requires `currency`

  `UmaAddressDestination.currency` on `POST /quotes` is now marked required in the spec.
  The API has always rejected a UMA address destination without it, so no working
  integration changes. Generated clients now enforce it at build time.

  ## Quote and transfer creation answer `200`

  `POST /quotes`, `POST /transfer-in` and `POST /transfer-out` have always answered
  `200 OK` for a newly created quote or transfer, and the spec now says so instead of
  `201 Created`. The `202 Accepted` reply for a quote awaiting Strong Customer
  Authentication is unchanged.

  ## `railDetails` on transaction sources and destinations

  Account and external-funding sources, and account destinations, carry a
  `railDetails` describing the rail transfer that moved funds for that side. It is a
  oneOf discriminated by `paymentRail`, and each rail has its own schema, status values,
  and bank account fields. It covers the rails below; a side that moved over any other
  rail has no `railDetails`, and the deprecated fields remain its source of rail
  details.

  | `paymentRail` | Schema | `status` values | Rail-specific fields |
  | - | - | - | - |
  | `ONCHAIN` | `OnChainRailDetails` | — | `transactionHash`, `network` |
  | `ACH`, `ACH_SAME_DAY` | `AchRailDetails` | `PENDING`, `SETTLED`, `RETURNED`, `RETURN_DISHONORED`, `RETURN_CONTESTED`, `REVERSED`, `FAILED` | `traceNumber`, `addenda`, `returnedAt`, `dishonoredAt`, `contestedAt`, `reversedAt` |
  | `WIRE` | `WireRailDetails` | `PENDING`, `SETTLED`, `FAILED` | `imad`, `originatorToBeneficiaryInformation` |
  | `FEDNOW` | `FedNowRailDetails` | `PENDING`, `SETTLED`, `FAILED` | `endToEndId`, `remittanceInformation` |
  | `RTP` | `RtpRailDetails` | `PENDING`, `SETTLED`, `FAILED` | `endToEndId`, `remittanceInformation` |

  `railDetails.status` reports the transfer on its rail; the transaction's top-level
  `status` reports where the funds are now, and its webhook events are unchanged. A returned
  ACH payout still moves to `FAILED` with `PAYOUT_RETURNED` and is refunded as before; its
  destination's `railDetails` now also shows `RETURNED` with `returnedAt`. An ACH deposit
  returned after `COMPLETED` moves to `FAILED` and sends `INCOMING_PAYMENT.FAILED`, with the
  source's `railDetails` showing `RETURNED`. A transaction stays `COMPLETED` through a return
  only when a later step that can't be undone already used the funds.

  `FAILED` is no longer described as terminal. On an incoming payment, a dishonored return
  restores the credit and moves the transaction back to `COMPLETED`. A returned payout stays
  `FAILED`, since it has already been refunded. Either way the ACH `railDetails` records the
  step: `RETURN_DISHONORED` with `dishonoredAt`, and `RETURN_CONTESTED` with `contestedAt` if
  the dishonor is contested. Only `EXPIRED` is terminal; apply each status webhook as it arrives. `TransactionStatus.REFUNDED`
  is deprecated: a refund is reported on the transaction's `refund` object.

  The flat originator fields on the external funding source (`accountHolderName`, `accountIdentifier`,
  `bankName`, `bankIdentifier`, `paymentRail`, `remittanceInformation`, `endToEndId`,
  `traceNumber`) are deprecated, as is `onChainTransaction`. They remain populated alongside
  `railDetails`, and on rails without a `railDetails` variant, such as SWIFT, they are the only
  source of the originator's details.
</Update>

<Update label="September 2026">
  ## Counterparty failures a retry can fix return `5xx`

  When a lookup or quote fails because of the counterparty and a retry could succeed, Grid
  now returns a `5xx` status. Some of these failures returned `400`, which reads as "your
  request was wrong". Existing error codes keep their names. Only their HTTP status moves.

  * `LOOKUP_REQUEST_FAILED` returns `504` instead of `400`. A network error or timeout
    stopped a receiver lookup from reaching the counterparty.
  * `QUOTE_REQUEST_FAILED` returns `504` instead of `400`. A network error or timeout
    stopped `POST /quotes` from sending the quote request to the counterparty.
  * `INVALID_PAYREQ_RESPONSE` returns `502` instead of `400`. The counterparty returned an
    invalid payment request.
  * The new `COUNTERPARTY_SERVER_ERROR` returns `502`. The counterparty answered a lookup
    or quote request with a server error. This includes a quote to a receiver whose
    provider is temporarily unavailable, which returned `400 INVALID_INPUT`.

  A lookup for a UMA address whose domain doesn't exist is a bad address, not a retryable
  failure. It now returns `404 USER_NOT_FOUND` instead of `400 LOOKUP_REQUEST_FAILED`.

  If you already retry on `5xx`, these are retried with no code-specific handling. If you
  detect them by matching `400` or `424`, match on `code` instead. See
  [Error handling](/payouts-and-b2b/payment-flow/error-handling).

  ## Sandbox test suffixes work on Solana addresses

  A sandbox external account's last three characters pick the test outcome, and every
  failure suffix starts with `00`. Base58 has no `0`, so no valid Solana address could end
  in one. The transfer and quote destination suffixes `002`–`008` now have `11X` twins: an
  address ending in `112` behaves like `002`, and `117` like `007`. The `00X` forms still
  work. See
  [Sandbox testing](/api-reference/sandbox-testing).

  ## `customerId` is required when creating a customer external account

  `POST /customers/external-accounts` has always rejected a request that omits
  `customerId`, and the spec now says so. Its request body is its own schema,
  `CustomerExternalAccountCreateRequest`, in which `customerId` is required. Use
  `POST /platform/external-accounts` to create an external account owned by the
  platform itself.

  ## Same-day ACH is its own payment rail

  `ACH_SAME_DAY` joins `ACH` in `PaymentRail`. Pin it on a quote's destination to request
  same-business-day settlement for a USD payout.

  * Grid never selects `ACH_SAME_DAY` for you. It is only used when you name it, so
    existing integrations that omit `paymentRail` are unaffected.
  * The two rails are priced separately. A `RailFeeConfig` with `rail: ACH_SAME_DAY`
    prices same-day sends; your existing `ACH` config keeps pricing standard sends.
  * `ACH_SAME_DAY` is capped at $1,000,000 per entry by the NACHA same-day limit, rising
    to $10,000,000 on 2027-09-17. A payout above the limit is rejected rather than slowed;
    send it over `ACH` instead.
  * `ACH` will settle on the standard next-business-day schedule. Until a date we
    announce in advance, `ACH` continues to settle same-business-day on production
    platforms; it already settles next-business-day on sandbox platforms. To guarantee
    same-day settlement after that date, request `ACH_SAME_DAY`.

  See [Assessing fees](/payouts-and-b2b/payment-flow/assessing-fees).

  ## Add cards to Apple Pay, Google Pay, and Samsung Pay from your app

  New `POST /cards/{id}/tokenize` exchanges the values a wallet SDK produces
  for the encrypted provisioning payload that completes an in-app "Add to
  Wallet" tap. The payload is encrypted to the wallet provider's keys, so
  card data never crosses your servers. `Card.cardCapabilities` gains
  `supportsDigitalWalletTokenization`. Manual entry into a wallet continues
  to work for every card with no integration. See
  [Digital wallet tokenization](/cards/card-management/digital-wallet-tokenization).

  ## Card refunds, declines, and voids report distinct outcomes

  Three card-statement corrections to `CardTransaction`:

  * **Refunds are their own dated rows.** A merchant `RETURN` against a purchase now opens
    its own `CardTransaction` (`direction: CREDIT`, credited value in `settledAmount`)
    linked back to the purchase via the new `originalTransactionId` field, instead of
    folding into the purchase's `refundedAmount`. The purchase stays `SETTLED`; the refund
    fires its own `CARD_TRANSACTION.*` webhooks on the refund row. To reverse a sandbox
    return, pass the refund row's id to `POST /sandbox/cards/{id}/simulate/return_reversal`.
  * **`DECLINED` status with `cardDeclinedReason`.** An authorization declined before any
    money moved now reports `DECLINED` (and fires `CARD_TRANSACTION.DECLINED`) instead of
    `EXCEPTION`, which is reserved for settlement anomalies. The new `cardDeclinedReason`
    field explains why Grid declined the authorization: `CARD_NOT_ACTIVE` (frozen or closed
    card), `SPEND_LIMIT_EXCEEDED`, `INSUFFICIENT_FUNDS` (sandbox only—production approves
    underfunded auths and resolves them as `EXCEPTION`), `NO_ELIGIBLE_FUNDING_SOURCE` (e.g.,
    Embedded Wallet with no delegated key), `BLOCKED` (merchant category or transaction
    blocked), `UNSUPPORTED_NETWORK`, or `OTHER`. Declines must be excluded from cardholder
    statements.
  * **`VOIDED` status.** An approved authorization that is fully reversed or expires
    before any clearing posts now reports `VOIDED` (and fires
    `CARD_TRANSACTION.VOIDED`) instead of `SETTLED`.
  * Card rows now carry the same full currency metadata (`decimals`, `name`, `symbol`) as
    payment rows, and `merchant` gains `city` and `state` when the network reports them.
  * `GET /transactions` accepts a `cardId` filter (`Card:` LSID or UUID) for listing one
    card of a multi-card cardholder, and rejects `status` combined with `type=CARD` with a
    400—`status` applies to payment transactions only.

  ## Record consent for each agreement by type

  `agreementConsents` supersedes `endUserTermsConsent`: a list with one entry per agreement,
  so a customer's acceptance of each document is recorded and auditable separately.

  * Each entry carries a `type` alongside the existing `acceptedAt`, `ipAddress`,
    `termsVersion`, and `acceptanceMethod` fields. Send at most one entry per type.
  * `GET /customers/agreements` lists every supported agreement with its `type`, current
    `version`, and hosted `url`. Send an entry's `version` as `termsVersion` when recording
    that agreement's acceptance. `GET /customers/end-user-terms` is deprecated but unchanged
    — it still returns the single `{ version, url }` object, so existing callers keep working.
  * Customer responses return `agreementConsents`, holding the most recent acceptance per
    accepted type, and an empty list until the first acceptance.
  * On update, supplying consents records additional acceptances; acceptances already on
    file for other types are left untouched.

  `endUserTermsConsent` is **deprecated, not removed** — it still works on both the request
  and response, and is equivalent to a single `LIGHTSPARK_END_USER_TERMS` entry. Acceptance
  you have already recorded is migrated for you and reported under that type; you don't need
  to re-collect it. Sending both fields in one request is rejected.

  Accepting one agreement never implies acceptance of another, so the remaining six types
  must be collected from each customer.

  See [Disclosures](/payouts-and-b2b/onboarding/disclosures).

  ## Card returns report as `SETTLED` with a `refundedAmount`

  `REFUNDED` is removed from `CardTransactionStatus`, and `CARD_TRANSACTION.REFUNDED` from
  the webhook types. A card transaction's status tracks authorization outcomes and
  settlement—a return is reported through `direction` and `refundedAmount`, which together
  already carry it.

  * A settled purchase that the merchant later returns stays `SETTLED`, with the returned
    value in `refundedAmount`. A standalone merchant refund arrives as `direction: CREDIT`
    and `status: SETTLED`, with the credited value in `settledAmount`.
  * Returns no longer get a webhook type of their own—the transaction re-fires
    `CARD_TRANSACTION.SETTLED` with the updated amounts.
  * The sandbox return reversal simulator requires a parent with a posted return (non-zero
    `refundedAmount`) rather than a `REFUNDED` parent, which the previous docs described
    and the server never produced.

  Nothing you have received changes: the status was always derived on read, so existing
  transactions already report `SETTLED`. `TransactionStatus.REFUNDED` on cross-border
  payments is a separate enum and is unaffected.

  See [Reconciliation](/cards/transactions/reconciliation).

  ## Sandbox KYC and KYB follow the production flow

  Unregulated sandbox platforms now resolve a customer's verification from a submitted
  packet rather than at create, so your sandbox integration rehearses the same collect,
  submit, and resolve loop you run in production.

  <Warning>
    Behavior change. On an unregulated sandbox, a name suffix alone no longer sets the
    result at create. New customers stay `UNVERIFIED` until you call `POST /verifications`.
    Customers created before this change keep their status, and regulated sandbox platforms
    are unaffected.
  </Warning>

  * Upload documents with `POST /documents`, register beneficial owners for a business, then
    submit with `POST /verifications`. The suffix on `fullName`, `registrationNumber`, or a
    beneficial owner's `lastName` still decides the outcome—it applies once the submission
    is in rather than at create.
  * An incomplete submission returns `verificationStatus: RESOLVE_ERRORS` naming every
    missing field and document, so you can fix and resubmit. A business whose
    `registrationNumber` ends in `002` now reports those errors first rather than rejecting
    outright.
  * Renaming a customer no longer overwrites a settled result—`APPROVED` and `REJECTED` stay
    put. Create a new customer when you want to exercise a different outcome.
  * To keep the previous fast path, open **Configuration** in your sandbox dashboard and turn
    on **Skip verification paperwork**. A terminal suffix then resolves with no documents:
    individuals at create, businesses at their first submission. `001` and `003` still require
    a complete packet. Turn it off when you want to test the full flow again.

  Walk through the fix-and-resubmit loop in [sandbox testing](/api-reference/sandbox-testing).
</Update>

<Update label="August 2026">
  ## `bankAccountType` is required on USD external accounts

  Creating an external account with `accountType: USD_ACCOUNT` now requires
  `bankAccountType`. Send `CHECKING` or `SAVINGS` on `POST /customers/external-accounts`,
  `POST /platform/external-accounts`, and `POST /agents/me/external-accounts`.

  * Grid uses `bankAccountType` to set the ACH transaction code. Accounts created without
    it were sent as checking, and the receiving bank returned a notification of change to
    correct savings accounts.
  * External accounts created before this change are unaffected.

  See [External accounts](/ramps/accounts/external-accounts) for a full request body.

  ## `/transfer-in` and `/transfer-out` are deprecated

  Same-currency transfers now go through the quote endpoint, so one integration covers
  same-currency and cross-currency alike.

  * Use `POST /quotes` with `immediatelyExecute: true` to create and execute a
    same-currency transfer in a single request.
  * `amount` becomes `lockedCurrencyAmount` with `lockedCurrencySide: "SENDING"`; source
    and destination gain `sourceType: "ACCOUNT"` and `destinationType: "ACCOUNT"`.
    `remittanceInformation`, `purposeOfPayment`, and the destination `paymentRail` carry
    over unchanged.
  * The response is a `Quote` rather than a `Transaction`—read `transactionId` from it to
    track the resulting transaction.
  * `POST /transfer-in` and `POST /transfer-out` continue to work with unchanged request
    and response shapes.

  See [Send a payment](/payouts-and-b2b/payment-flow/send-payment#send-a-payment)
  for the updated flow.

  ## Assess your own fees on every transaction

  Charge your customers a platform fee and keep the margin—Grid collects it for you and
  reports it separately from network and FX costs.

  * Set a standing fee with `feeConfigs` on `PATCH /config`—a variable rate in basis
    points, a fixed amount, or both, applied to cross-currency transactions.
  * Override it on a single transaction with `platformFeeOverride` on `POST /quotes`, for
    promos, VIP pricing, or negotiated rates. No standing config required.
  * Both the standing config and the per-transaction override require a USD source
    currency today.
  * Reconcile with `platformFeesIncluded` on quotes and `platformFees` on outgoing
    transactions—your cut, broken out of the total.

  See [Fees](/platform-overview/core-concepts/quote-system#fees) for exactly how the
  variable fee is assessed on each locked side.

  ## Record end-user terms acceptance

  Unregulated platforms must record that each customer accepted Grid's end-user terms
  before their account opens.

  * `GET /customers/end-user-terms` returns the current terms URL and version.
  * Pass `endUserTermsConsent` on customer create or update with the timestamp, IP address,
    terms version, and acceptance method.
  * Quotes fail with `END_USER_TERMS_NOT_ACCEPTED` until acceptance is on file.

  See [Disclosures](/payouts-and-b2b/onboarding/disclosures).

  ## Run KYC from your own onboarding form

  Collect verification data in your own UI and submit it programmatically instead of
  handing customers to a hosted flow.

  * Enhanced due diligence—source of funds and wealth, purpose of account, expected
    transaction count and volume, income and net worth ranges, PEP status—is now a set of
    optional fields on the individual customer itself. Send them on `POST /customers` or
    add them later with `PATCH /customers/{customerId}`; there is no separate EDD resource.
  * `POST /verifications` returns `RESOLVE_ERRORS` with one entry per problem, each naming
    the exact field or accepted document types still needed, so you can fix and resubmit
    without guessing.
  * Request validation errors return every invalid field at once in
    `Error400.details.errors[]`, each with a machine-readable constraint you can render as
    field-level UX—no more resubmitting to discover the next error.
  * Individual customers and beneficial owners now share one identification vocabulary:
    `idType`, `identifier`, and `countryOfIssuance`.

  Rehearse the fix-and-resubmit loop in [sandbox testing](/api-reference/sandbox-testing).

  ## Groundwork for EU support: SCA and Travel Rule

  The API surface for the two controls EU regulation requires is now defined, so you can
  design against it ahead of EU corridors opening.

  * **Strong Customer Authentication**—`POST /sca/login/complete` returns
    `sessionExpiresAt`, so you can prompt a re-login before the 180-day session lapses
    rather than discovering it as a failed payment. Quote authorization documents
    `SCA_SESSION_REQUIRED` (409) and `ACCOUNT_LOCKED` (423), and `SCA_NOT_COMPLETED`
    explains a transaction that failed on an expired challenge.
  * **Travel Rule ownership verification**—a challenge and verify flow for proving a
    customer owns a self-custody wallet. `POST …/external-accounts/{id}/challenge` starts
    verification by wallet signature or hosted liveness check, and `…/verify` completes a
    signature synchronously. Accounts sit at `PENDING_OWNERSHIP_VERIFICATION`—still usable
    below regulatory thresholds—and move to `ACTIVE` on success or `UNVERIFIED` on a failed
    attempt. A single webhook, `EXTERNAL_ACCOUNT.STATUS_UPDATED`, covers the whole
    lifecycle.

  EU availability will be announced separately.

  ## Issue your own stablecoin

  Register a stablecoin with `/stablecoins`, then mint and burn directly against it with
  `/stablecoins/{stablecoinId}/mints` and `/stablecoins/{stablecoinId}/burns`.
  `/stablecoins/{stablecoinId}/operations` tracks each issuance through settlement.

  ## More ways to move money

  * **USDT on Ethereum and Plasma**, alongside Tron.
  * **Bitcoin L1** deposit addresses as a payment instruction on quotes.
  * USD accounts can describe a full wire beneficiary—bank name, checking or savings,
    intermediary bank and routing number, and bank-to-bank instructions.
  * Businesses outside the US can register as a publicly listed company, trust, private
    foundation, or charity.

  See [Currencies and rails](/platform-overview/core-concepts/currencies-and-rails).

  ## Track crypto settlement to and from external wallets

  A transaction that settles on-chain to or from an external crypto wallet now carries the
  settled transfer inline: `onChainTransaction` on the transaction's source or destination
  reports the transfer's `transactionHash` and `network` once the crypto transfer settles.

  <Warning>
    Deprecation. `reconciliationInstructions.transactionHash` is deprecated for wallet
    transfers—read `onChainTransaction` instead, which also tells you the network. The field
    keeps reporting the inter-VASP settlement leg of a UMA payment, which has no
    `onChainTransaction` equivalent, and will not be removed before that leg has a
    replacement.
  </Warning>

  ## Know why a payment failed

  Failure reasons now name outcomes you act on rather than internal processing steps.

  <Warning>
    Breaking change. `EXECUTION_FAILED_POST_DEBIT` and `SETTLEMENT_FAILED` are removed and
    collapse into `QUOTE_EXECUTION_FAILED`, whose description now covers the whole path to
    settlement and states that a debited amount is refunded automatically. `TIMEOUT` and
    `MANUAL_REFUND` are removed because that outcome already surfaces on the refund object.
  </Warning>

  * New payout failure reasons: `PAYOUT_RETURNED`, `LIMIT_EXCEEDED`,
    `ACCOUNT_CANNOT_RECEIVE`, `ACCOUNT_INVALID`, and `COMPLIANCE_REJECTED`.
  * `pendingReason` on any transaction tells you when it is held for compliance review or
    waiting on customer action.
  * New limit codes: `TRANSACTION_SIZE_LIMIT_EXCEEDED` (400) and
    `DAILY_VOLUME_LIMIT_EXCEEDED` (429).

  See [Transaction lifecycle](/platform-overview/core-concepts/transaction-lifecycle#failure-handling).
</Update>

<Update label="July 2026">
  ## Card issuance in sandbox

  You can now issue cards directly in the sandbox environment. Test the full card
  lifecycle—[cardholder setup](/cards/onboarding/cardholder-setup),
  [issuing cards](/cards/card-management/issuing-cards),
  [funding sources](/cards/card-management/funding-sources), and
  [freezing and closing](/cards/card-management/freezing-and-closing)—end to end
  before going live, without touching production.

  See [Cards sandbox testing](/cards/platform-tools/sandbox-testing) to get started.

  ## Cards: real-time webhooks and full event simulation

  React to card activity as it happens, and rehearse every event before going live.

  * `CARD_TRANSACTION.*` fires on every state transition—authorized, partially settled,
    settled, refunded, and exception—carrying the full card transaction.
  * All ten sandbox simulate endpoints, covering balance inquiries, credit and financial
    authorizations, authorization advices, returns, and return reversals.
  * Brand the tokenization verification codes your customers receive with
    `cardTokenization2faConfig` on platform config.

  See [Card webhooks](/cards/platform-tools/webhooks).

  ## Refund visibility on incoming payments

  `INCOMING_PAYMENT.REFUND_PENDING`, `INCOMING_PAYMENT.REFUND_COMPLETED`, and
  `INCOMING_PAYMENT.REFUND_FAILED` now fire the same way outgoing refunds already did, so
  a returned deposit no longer surfaces only as a failed transaction.

  See [Refund object](/platform-overview/core-concepts/transaction-lifecycle#refund-object).

  ## Expanded country coverage

  Grid now supports additional countries, including **China**, broadening the corridors
  available for global payments. Review the currencies and rails available in each region
  in [Currencies and rails](/platform-overview/core-concepts/currencies-and-rails).
</Update>


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.