> ## Documentation Index
> Fetch the complete documentation index at: https://docs.y.uno/llms.txt
> Use this file to discover all available pages before exploring further.

# onStatus

> Receives the final status of the transaction.

`onStatus` is invoked when the transaction reaches its final state. It is the only way to know how the transaction ended.

On iOS, `onStatus` can run more than once for one transaction. Asynchronous methods (PIX, boleto, OXXO, bank transfers and similar) report `status` `PENDING` as soon as the payment exists and the shopper has the payment instructions, with the backend `substatus` when there is one. The final status arrives in a later call, once Yuno confirms the payment. If the shopper closes the instructions screen, the SDK reports `PENDING` with `substatus` `CLOSED_BY_USER`. Treat `PENDING` as not final: keep your screen in a waiting state and confirm the outcome on your server.

On Web, these deliveries are not final. The form stays mounted after them, so the shopper can try again:

* `INTERNAL_ERROR` with `message.code` `FORM_INVALID`. You submitted a form that is not valid, with [`transaction.submit()`](/docs/yuno-sdk/controllers/show-view#web-submit-from-your-own-button) or with `transaction.start({ paymentMethod })` on the payment methods list.
* `DECLINED` or `ERROR` for a card payment the SDK can retry, when you override `createPayment`. The SDK moves the form to a new checkout session, shows the field errors and keeps the form. Enable your pay button again. The next `submit()` or `start()` pays on the new session, which `token.checkout_session` carries into `createPayment`. Without `createPayment`, a form mounted by `start()` has no in-place retry: the decline is final and reaches `onStatus`. On the payment methods list and with secure fields the SDK retries in place and does not call `onStatus` for the declined attempt.
* `INTERNAL_ERROR` with `message.code` `COLLECT_FAILED`, when you override `createPayment` and a Mercado Pago wallet fails before it produces a token. The wallet button stays mounted.

## Behavior

This controller has no default behavior. If you don't implement it, you won't receive the final status. On Web, `transaction()` also prints a console warning when the config has no `onStatus` function. When implemented, the SDK delivers the final status to your handler after tokenization, payment creation (or merchant-side payment creation via `createPayment`), 3DS, and status resolution have completed.

## Signature

<Tabs>
  <Tab title="Web">
    Wire it as a field on the config.

    ```typescript theme={"theme":{"light":"github-dark","dark":"github-dark-dimmed"}}
    sdkPayments.transaction({
      ...,
      onStatus: (result) => console.log(result.status),
    })
    ```

    `result` carries `status`, `substatus` and `message`. `status` and `substatus` are the backend values, with `substatus` as `null` when the backend sent none. On a `DECLINED`, `REJECTED`, `EXPIRED`, `CANCELED` or `ERROR` payment, `message` is `{ source: 'backend', code: <response_code> }` when the backend sent a response code, and `null` otherwise. Every other backend status, on payment and enrollment flows, carries `message: null`. A status value this SDK version does not know is not passed through. It arrives as `INTERNAL_ERROR` with `message.code` `UNSUPPORTED_STATUS` and `message.reason` set to `unsupported status: <value>`. It is the same payload shape on payment and enrollment flows. There is no separate result type per flow.
  </Tab>

  <Tab title="iOS">
    ```swift theme={"theme":{"light":"github-dark","dark":"github-dark-dimmed"}}
    func onStatus(status: SdkPayments.TransactionStatus)
    ```

    `SdkPayments.TransactionStatus` carries `status` and `substatus` (the backend values, passed through verbatim for the statuses this SDK version knows) and an optional `message` populated on error outcomes. A status the SDK does not know arrives as `INTERNAL_ERROR` with `message.code` `UNSUPPORTED_STATUS`, as on Web. The `message.code` values the SDK itself emits are listed in [Status](/docs/yuno-sdk/reference/objects-you-receive#status).
  </Tab>

  <Tab title="Android">
    ```kotlin theme={"theme":{"light":"github-dark","dark":"github-dark-dimmed"}}
    fun onStatus(status: TransactionStatus)
    ```
  </Tab>
</Tabs>

See [`Status`](/docs/yuno-sdk/reference/objects-you-receive#status) for the shape of the payload, the `message` field, and the SDK-emitted values (`INTERNAL_ERROR`, `USER_CANCEL`).

On Web, an `INTERNAL_ERROR` with `message.source` `'sdk'` can carry values such as these in `message.code`:

* `FORM_INVALID`. The submitted form is not valid. Not final, see above.
* `COLLECT_FAILED`. A wallet failed before it produced a token. `message.reason` names the cause.
* `UNSUPPORTED_STATUS`. The backend returned a status this SDK version does not know. `message.reason` is `unsupported status: <value>`.
* `ELEMENT_NOT_FOUND`. The element passed to `paymentMethodList.mount()` is not in the document, or the form's container was removed while the form was loading. A config `elementSelector` that matches nothing makes `start()` reject with `elementSelector "<selector>" was not found in the document.` instead.
* `UNEXPECTED_ERROR`. A failure the SDK does not classify, including an SDK chunk that failed to download. `message.reason` carries the error message.

With `message.source` `'backend'`, `message.code` is the backend error code, or the HTTP status when the response body has none. Branch on `message.code` for the cases you handle and treat the rest as a generic failure.

On Web, `USER_CANCEL` also arrives when you call `transaction.unmount()` while a form is mounted and the transaction has not delivered a status yet, unless a payment is in flight. Unmount after `onStatus` has fired, or treat that delivery as the shopper leaving the payment.

## Confirm on your server

<Warning>
  `onStatus` is a client-side signal. Use it to drive your UI, never as the source of truth for fulfillment.
</Warning>

`onStatus` runs on the user's device, so it can be missed (the app is killed, the network drops, the user closes the page mid-flow) or manipulated (anything running on the client can be tampered with). A `SUCCEEDED` delivered to your handler is not proof that money moved.

Before shipping goods, granting access, or marking an order as paid, confirm the outcome on your backend:

* **Webhooks (recommended).** Configure [webhooks](/docs/webhooks) so Yuno notifies your backend of every payment status change, independently of what happens on the client. Verify the notification signature before trusting it.
* **Query the API.** Have your backend call [Retrieve Payment by ID](/reference/payments/retrieve-payment-by-id) (or [by `merchant_order_id`](/reference/payments/retrieve-payment-by-merchant-order-id)) and read the status from the response.

Treat `onStatus` as the trigger to update the screen, and the server-side confirmation as the trigger to fulfill.

## Threading

On iOS and Android, `onStatus` is invoked on the main thread. You can update UI directly from inside this handler.

## Related

* [`Status`](/docs/yuno-sdk/reference/objects-you-receive#status). Values and substatus information.
* [Transactions](/docs/basic-concepts/transactions). How a payment flows through Yuno.
* [Payment status values](https://docs.y.uno/reference/payments/status-and-response-codes/payment). Status and substatus reference.
* [`showStatusScreen`](/docs/yuno-sdk/reference/objects-you-pass#showstatusscreen). Flag to enable or disable the SDK's built-in result screen.


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