# Callback payloads

What onComplete and onError receive, with example payloads.

`onComplete` receives the full transaction object for transactions that reached the payment gateway - both approved and declined. `onError` receives a standard error object for SDK errors, session errors, and other non-transaction failures.

| Callback     | Fires when                                                                        | Payload                                                    |
| ------------ | --------------------------------------------------------------------------------- | ---------------------------------------------------------- |
| `onReady`    | The form is ready                                                                 | `{ sessionId }`                                            |
| `onComplete` | A transaction reached the gateway                                                 | Full transaction object with a `success` flag              |
| `onError`    | An SDK, session, or processing error occurred; the gateway outcome may be unknown | Error object with `code`, `status`, `message`, `timestamp` |

> **Important:**
>
> `onComplete` fires for declines too. Check `transaction.success` - `false` means the SDK did not classify the result as successful. Inspect `resultCode` and `transactionResponses`; it can be a decline or a processing failure. Do not treat the callback firing as proof of payment.

For ACH, `success: true` can mean the transfer was accepted with `status: Processing` and `resultCode: 0`; it does not confirm settlement. Confirm the final outcome through your backend before fulfilling an order.

## Example payloads

**Approved**

```json
{
  "success": true,
  "id": "trx_01J2F0EKHC7HY2R93C8ENBD1FG",
  "timestamp": "2025-06-02T23:56:18.2102020Z",
  "type": "Sale",
  "status": "Completed",
  "referenceId": "ref_s192i49i",
  "orderNumber": "order_number_1234",
  "invoiceNumber": "inv_12345678",
  "requestedAmount": 1000,
  "approvedAmount": 1000,
  "balanceAmount": 0,
  "paymentMethod": {
    "id": "pmt_vrt_01JRZPTWS99Z7RB57Q1CVWSWDS",
    "type": "Virtual",
    "currency": "USD",
    "description": "Online Checkout Iframe"
  },
  "accountHolder": {
    "id": "aho_01JRZPRGFF4J2SZC3HMDBYEN2J",
    "externalId": "ext_customer_123",
    "contact": {
      "name": "Jane Doe",
      "countryCode": "US",
      "zipCode": "30303",
      "address": "123 Peachtree St",
      "address2": "Suite 200",
      "state": "GA",
      "city": "Atlanta"
    }
  },
  "transactionResponses": [
    {
      "responseCode": 1,
      "amountApproved": 1000,
      "cardType": "VISA",
      "receipt": { "lines": [] },
      "paymentMethod": {
        "id": "pmt_tkn_01JRZPTMTBN41PC3VPQNZ5T3HF",
        "type": "Token",
        "currency": "USD"
      }
    }
  ],
  "resultCode": 0,
  "resultText": "APPROVED"
}
```

**Declined**

```json
{
  "success": false,
  "id": "trx_01J2F0EKHC7HY2R93C8ENBD1FG",
  "timestamp": "2025-06-02T23:56:18.2102020Z",
  "type": "Sale",
  "status": "Completed",
  "referenceId": "ref_s192i49i",
  "orderNumber": "order_number_1234",
  "invoiceNumber": "inv_12345678",
  "requestedAmount": 1000,
  "approvedAmount": 0,
  "balanceAmount": 0,
  "paymentMethod": {
    "id": "pmt_vrt_01JRZPTWS99Z7RB57Q1CVWSWDS",
    "type": "Virtual",
    "currency": "USD",
    "description": "Online Checkout Iframe"
  },
  "transactionResponses": [
    {
      "responseCode": 10,
      "amountApproved": 0,
      "cardType": "VISA",
      "receipt": { "lines": [] }
    }
  ],
  "resultCode": 6900,
  "resultText": "DECLINED"
}
```

**Session error**

```json
{
  "code": 3201,
  "status": "Rejected",
  "message": "Failed to retrieve session data",
  "traceId": "1-6838bcce-5c0074e82ac7170d4f990d87",
  "timestamp": "2025-06-02T23:56:18.2102020Z"
}
```

The SDK assigns `Rejected` to session errors (`3200`-`3299`). A session can fail to load because it was already opened or used. If an earlier attempt may have submitted a payment, reconcile that attempt before creating a new session.

**SDK error**

```json
{
  "code": 6000,
  "status": "Interrupted",
  "message": "Payment processing failed",
  "timestamp": "2025-06-02T23:56:18.2102020Z"
}
```

`status: Interrupted` means the final outcome is unknown. Resolve it with [`GET /transactions`](https://doura.ca/docs/payments/transaction-retrieval.md) before retrying.

## Handling the four cases

```javascript
const callbacks = {
  onComplete: (transaction) => {
    if (transaction.success) {
      showPaymentAccepted(transaction); // backend confirms before fulfillment
    } else {
      showPaymentOutcome(transaction); // inspect the result before offering a retry
    }
  },
  onError: (error) => {
    reconcileBeforeRetrying(error); // check any earlier submission first
  },
};
```

Configured transaction webhooks report gateway outcomes. A local validation error or a session that never reaches the gateway does not guarantee a transaction webhook. Use authenticated transaction retrieval when an outcome is uncertain. Treat callbacks as customer interface signals.

### A cancel is not proof that no payment occurred

The customer can cancel only before the payment is submitted. If the payment has already reached the gateway, the cancel is refused, but the SDK still reports a cancellation: `onError` with `code: 6000`, `status: "Interrupted"`, and `message: "User cancelled"`, and a return URL with `success=false&resultCode=6000&errorCode=6000` and no `hash` or `transactionId`. The payment can still complete. Wait for the `sale.completed` or `auth.completed` webhook, or query the transaction by `referenceId`, before you treat the order as unpaid.

A cancel that was accepted can also be followed by a real outcome when the gateway reports the payment later: the same `trx_` transaction changes from the canceled record to the gateway's outcome, and the webhook is sent then. See [Event catalog](https://doura.ca/docs/webhooks/events.md#transaction-events).

### What the customer sees

After a failed card or wallet payment, the form shows the customer `This card could not be processed. Please start a new payment with a different card.` instead of the gateway's text. ACH and EFT show `resultText`. Your callbacks, the return URL, and the webhook still carry the gateway's `resultText` and `resultCode`.

## Related

* [SDK error codes](https://doura.ca/docs/payment-sdk/error-codes.md)
* [Errors and rate limits](https://doura.ca/docs/get-started/errors-and-rate-limits.md#status-is-what-tells-you-whether-to-retry)
