> For the complete documentation index, see [llms.txt](https://docs.idrx.co/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.idrx.co/api/callback.md).

# Callback

Callback allows you to receive a notification when a minting or redemption transaction is completed. To use Callback, you must first generate an [**API Key**](/api/getting-started.md). Once you have your API Key, navigate to the **API Key** menu and select the **Callback URL** tab to set it up.

<figure><img src="/files/m0xAbfV8DFuARhHyM15H" alt=""><figcaption></figcaption></figure>

Enter your Mint and Redeem Callback URLs. You may use a single endpoint for both callbacks if preferred. Once done, click the **Save Callback URL** button to confirm.

<figure><img src="/files/RgY2hkzeyuxfzkrI5ZtA" alt=""><figcaption></figcaption></figure>

You’ll see a notification in the bottom-right corner once the Callback URL is successfully saved.

<figure><img src="/files/m7AMONAFeEbV8YprxPNy" alt=""><figcaption></figcaption></figure>

You can then use the endpoint to receive callbacks in your logs whenever there’s an update to your mint or redeem transactions. Please refer to the example below.

### Delivery rules

These rules apply to both mint and redeem callbacks:

* **One callback per terminal state.** The mint callback fires once when `adminMintStatus` transitions to `MINTED`. If your endpoint is unreachable, the callback is **not** retried automatically — respond with `2xx` quickly and process asynchronously on your side.
* **No signature on the outgoing webhook.** Reconcile by `merchantOrderId` against your own state. Treat the request as untrusted until you've matched it and re-verified via Transaction History API.
* **Body is `application/json`.** Headers are minimal — only the standard `Content-Type`.
* **Two mint flavours:**
  * **IDRX mint** — when `requestType` is empty or `"idrx"`. Fires after IDRX is minted on-chain.
  * **USDT onramp** — when `requestType` is `"usdt"`. Fires after the IDRX→USDT swap and USDT transfer to the destination wallet succeed.

{% hint style="warning" %}
Since callbacks are not retried, missed deliveries must be reconciled by polling the Transaction History API using `merchantOrderId`.
{% endhint %}

***

## Mint callback

### Payload structure

Every mint callback body has these top-level fields:

| Field                        | Type           | Description                                                |
| ---------------------------- | -------------- | ---------------------------------------------------------- |
| `id`                         | number         | Internal MintRequest ID                                    |
| `paymentAmount`              | number         | Amount the user paid in IDR (before fees)                  |
| `toBeMinted`                 | string         | IDRX amount minted (= `paymentAmount` minus deducted fees) |
| `merchantOrderId`            | string         | Your order ID                                              |
| `destinationWalletAddress`   | string         | Wallet that received the IDRX or USDT                      |
| `chainId`                    | number         | EVM chain ID                                               |
| `paymentStatus`              | enum           | `WAITING_FOR_PAYMENT` / `PAID` / `EXPIRED`                 |
| `userMintStatus`             | enum           | `PROCESSING` / `MINTED` / `NOT_AVAILABLE`                  |
| `adminMintStatus`            | enum           | `REQUESTED` / `PROCESSING` / `MINTED` / `REJECTED`         |
| `txHash`                     | string         | On-chain mint transaction hash                             |
| `reference`                  | string         | Payment gateway reference                                  |
| `requestType`                | string         | `null` / `"idrx"` / `"usdt"`                               |
| `customerVaName`             | string         | Source account name                                        |
| `paymentProviderId`          | number         | Always `1` (POP)                                           |
| `returnUrl`                  | string \| null | Echoed back from original request                          |
| `expiryTimestamp`            | string         | Unix epoch milliseconds                                    |
| `isApproved`                 | boolean        | Multisig approval flag                                     |
| `reportStatus`               | enum           | `NONE` / `PENDING` / `RESOLVED`                            |
| `processByUserId`            | number \| null | Admin processor                                            |
| `qredoTxId`                  | string \| null | Safe multisig transaction hash                             |
| `signedTx`                   | string \| null | Solana signed transaction blob                             |
| `deleted`                    | boolean        | Soft-delete flag                                           |
| `createdAt`                  | ISO 8601       | Creation timestamp                                         |
| `updatedAt`                  | ISO 8601       | Last updated timestamp                                     |
| `TransactionHistory`         | object         | Payment gateway notification                               |
| `MintRequestTransactionFees` | array          | Fee breakdown                                              |
| `usdtRequest`                | object \| null | USDT swap details                                          |

***

### `TransactionHistory` sub-object

```jsonc
{
  "id": 21468,
  "merchantCode": "D11808",
  "amount": "55000",
  "merchantOrderId": "20260508154603",
  "productDetail": "Deposit USD",
  "additionalParam": "toBeMinted: 55000 destinationWalletAddress: 0x...",
  "resultCode": "00",
  "signature": "4f9cabb4aac2b04fbc02a3b039a906fc",
  "paymentCode": "BR",
  "merchantUserId": 5002,
  "reference": "D11808261VSEBJBL43V2ZIW",
  "spUserHash": null,
  "issuerCode": null,
  "settlementDate": "2026-05-09",
  "publisherOrderId": "BR26BOLMOR7DDYK18WA",
  "sourceAccount": "",
  "deleted": false,
  "createdAt": "2026-05-08T15:47:58.487Z",
  "updatedAt": "2026-05-08T15:47:58.487Z"
}
```

***

### `MintRequestTransactionFees` array

```jsonc
{
  "name": "QRIS Fee (0.7%)",
  "amount": "609"
}
```

{% hint style="warning" %}
An empty array (`[]`) means the payment channel charges no IDRX-layer fee.\
This is expected behaviour, not missing data.
{% endhint %}

***

### `usdtRequest` sub-object (USDT onramp only)

```jsonc
{
  "id": 13397,
  "mintRequestId": 33685,
  "merchantOrderId": "20260508154603",
  "destinationWalletAddress": "0xadbe359255d723a5ad3706395b446aed5cf285e6",
  "chainId": 8453,
  "amountIdrx": 55000,
  "usdtRequested": "3.174996",
  "amountUsdt": "3.174996",
  "swapTxHash": "0x8a4b781e8c8694bc68906bb85206da174fe3550776e72cbe806f19931732030d",
  "txHash": "0x55b16ff8100f12e9c611f13a7848d16917b477a5d838172c26e3b0d6da1e8617",
  "status": "SUCCESS",
  "deleted": false,
  "createdAt": "2026-05-08T15:46:03.272Z",
  "updatedAt": "2026-05-08T15:49:04.769Z"
}
```

***

## Fee structure by payment method

Two patterns exist depending on the channel:

* **VA & e-wallet**
  * Fee is added on top at checkout.
  * `paymentAmount === toBeMinted`
  * `MintRequestTransactionFees = []`
* **QRIS**
  * Fee is deducted from minted IDRX.
  * `toBeMinted = paymentAmount - 0.7%`
  * Fee row appears in `MintRequestTransactionFees`

| Channel      | `paymentCode` | IDRX fee  | Charged how  | Effect           |
| ------------ | ------------- | --------- | ------------ | ---------------- |
| BNI VA       | `IQ`          | 3,000 IDR | Added on top | Fees array empty |
| BRI VA       | `BR`          | 3,000 IDR | Added on top | Fees array empty |
| OVO          | `OV`          | 1.67%     | Added on top | Fees array empty |
| DANA         | `DA`          | 1.67%     | Added on top | Fees array empty |
| NusaPay QRIS | `SQ`          | 0.7%      | Deducted     | Fee row present  |
| NOBU QRIS    | `NQ`          | 0.7%      | Deducted     | Fee row present  |

***

### Worked example — 100,000 IDR top-up

| Channel             | User pays | IDRX delivered | `paymentAmount` | `toBeMinted` | Fees                                               |
| ------------------- | --------- | -------------- | --------------- | ------------ | -------------------------------------------------- |
| BRI VA (`BR`)       | 103,000   | 100,000        | `100000`        | `"100000"`   | `[]`                                               |
| OVO (`OV`)          | 101,670   | 100,000        | `100000`        | `"100000"`   | `[]`                                               |
| NusaPay QRIS (`SQ`) | 100,000   | 99,300         | `100000`        | `"99300"`    | `[{ "name": "QRIS Fee (0.7%)", "amount": "700" }]` |

Universal reconciliation formula:

```ts
const idrxFee =
  body.paymentAmount -
  parseInt(body.toBeMinted, 10);
```

***

## Mint examples

### A — IDRX mint via QRIS (`SQ`)

```json
{
  "id": 33494,
  "paymentAmount": 86967,
  "toBeMinted": "86358",
  "merchantOrderId": "20260506070155",
  "destinationWalletAddress": "0xA3aE7A9562E6DDdc693be0c34013637730E2bA00",
  "chainId": 8453,
  "paymentStatus": "PAID",
  "userMintStatus": "MINTED",
  "adminMintStatus": "MINTED",
  "txHash": "0x9a3c5b...e2f01a",
  "reference": "D11808260X8QYZJZQ19K5",
  "requestType": "idrx"
}
```

***

### B — IDRX mint via BRI VA (`BR`)

```json
{
  "id": 33687,
  "paymentAmount": 100000,
  "toBeMinted": "100000",
  "merchantOrderId": "20260509093000",
  "destinationWalletAddress": "0xA3aE7A9562E6DDdc693be0c34013637730E2bA00",
  "chainId": 8453,
  "paymentStatus": "PAID",
  "userMintStatus": "MINTED",
  "adminMintStatus": "MINTED",
  "txHash": "0x4b1f8d...c91e72",
  "reference": "D11808261VKLMNO78PQRS",
  "requestType": "idrx",
  "MintRequestTransactionFees": []
}
```

{% hint style="info" %}
Empty `MintRequestTransactionFees` on VA payments is expected behaviour.
{% endhint %}

***

### USDT onramp variant

When `requestType === "usdt"`:

* Callback fires after the IDRX → USDT swap completes
* Payload shape is identical
* `usdtRequest` becomes populated

```json
{
  "id": 13397,
  "mintRequestId": 33685,
  "merchantOrderId": "20260508154603",
  "destinationWalletAddress": "0xadbe359255d723a5ad3706395b446aed5cf285e6",
  "chainId": 8453,
  "amountIdrx": 55000,
  "usdtRequested": "3.174996",
  "amountUsdt": "3.174996",
  "swapTxHash": "0x8a4b781e8c8694bc68906bb85206da174fe3550776e72cbe806f19931732030d",
  "txHash": "0x55b16ff8100f12e9c611f13a7848d16917b477a5d838172c26e3b0d6da1e8617",
  "status": "SUCCESS"
}
```

***

## Reconciliation tips

```ts
// Universal fee calculation
const idrxFee =
  body.paymentAmount -
  parseInt(body.toBeMinted, 10);

// IDRX delivered
const idrxMinted =
  body.usdtRequest
    ? null
    : body.toBeMinted;

// USDT delivered
const usdtDelivered =
  body.usdtRequest?.amountUsdt;

// Effective exchange rate
const rate =
  body.usdtRequest?.amountUsdt
    ? body.paymentAmount /
      parseFloat(body.usdtRequest.amountUsdt)
    : null;
```

For high-value transactions, verify on-chain delivery using `txHash`.

***

## Status handling

Always check `adminMintStatus`.

| Status        | Meaning             | Action         |
| ------------- | ------------------- | -------------- |
| `MINTED`      | Tokens delivered    | Mark success   |
| `REJECTED`    | Expired or rejected | Mark cancelled |
| anything else | Unexpected          | Log and alert  |

***

## Redeem callback

### IDRX

```json
{
  id: 1234,
  chainId: 56,
  userId: 1234,
  requester: 'NUR MUHAMMAD LUTHFI',
  txHash: '0xabcdefghijk',
  fromAddress: '0xlmnopqrstuvwxyz',
  amount: '15000',
  bankName: 'BANK JAGO',
  bankCode: '542',
  bankAccountNumber: '123456789',
  bankAccountName: 'NUR MUHAMMAD LUTHFI',
  bankAccountNumberHash: null,
  custRefNumber: '123456789',
  disburseId: 123456789,
  burnStatus: 'SUCCESS',
  createdAt: '2025-07-29T10:48:50.417Z',
  updatedAt: '2025-07-29T10:49:54.691Z',
  deleted: false,
  reportStatus: 'NONE',
  notes: ' | ',
  RedeemRequestTransactionFees: [
    {
      id: 1234,
      name: 'Payment Gateway Fee',
      amount: '5000',
      redeemRequestId: 3456,
      deleted: false
    }
  ]
}
```

***

### USDT / USDC / USDT0

```json
{
  id: 4593,
  chainId: 1135,
  userId: 2255,
  requester: 'NUR MUHAMMAD LUTHFI',
  txHash: '0x8359c8901667923ee0d214fd5100e0b99daea63d5acda2de276cff584461f16c',
  fromAddress: '0x55360Fe4179936e88c321f6e3bd23Ef34E916110',
  amount: '36219',
  bankName: 'GOPAY',
  bankCode: '1011',
  bankAccountNumber: '08123456789',
  bankAccountName: 'GOPAY Nxx Mxxxxxxx Lxxxxx',
  bankAccountNumberHash: null,
  custRefNumber: '000045642625',
  disburseId: 30098713,
  burnStatus: 'SUCCESS',
  createdAt: '2025-08-01T06:50:53.109Z',
  updatedAt: '2025-08-01T06:51:52.473Z',
  deleted: false,
  reportStatus: 'NONE',
  notes: null,
  RedeemRequestTransactionFees: [
    {
      id: 3217,
      name: 'Payment Gateway Fee',
      amount: '5000',
      redeemRequestId: 5943,
      deleted: false
    }
  ],
  depositRedeemRequest: {
    id: 208,
    chainId: 1135,
    userId: 2255,
    address: '0x1095bBe769fDab716A823d0f7149CAD713d20A13',
    toAddress: '0x55360Fe4179936e88c321f6e3bd23Ef34E916110',
    transferTxHash: '0x3e36838c61f3c9f2009d71b1b53dafc1d8eb280bf490e58ab08dee9767dcc842',
    tokenFrom: 'usdt',
    amountFrom: '2.511252',
    tokenTo: 'idrx',
    amountTo: '41219',
    swapTxHash: '0x15187b051f8f6b9da02470cf548f395199429442c445a52ebee44698fad9de93',
    burnTxHash: '0x8359c8901667923ee0d214fd5100e0b99daea63d5acda2de276cff584461f16c',
    status: 'SUCCESS',
    createdAt: '2025-08-01T06:49:06.406Z',
    updatedAt: '2025-08-01T06:50:53.118Z',
    deleted: false
  }
}
```

***

## See also

* [POST /api/transaction/mint-request](/api/transaction-api/post-api-transaction-mint-request.md) — how mint orders are created.
* [Processing Mint IDRX Requests](/integration/processing-mint-idrx-requests.md) — end-to-end mint flow with reconciliation pattern.
* [Transaction History API](/api/transaction-api/get-api-transaction-user-transaction-history.md) — pull state by `merchantOrderId` if a webhook is missed.
