Track payouts
Use this component to track a customer's payouts in your platform. Nium renders the transaction history in an iFrame with transaction ID search, period, type, and status filters, and a detail view for each transaction. Your app never handles raw transaction data. The element type is transactionHistoryForm.
This component:
- Shows a paginated list of the customer's wallet transactions, with date, account, type, from and to, amount, and status. A per-transaction detail view includes the status timeline, and settled transactions have a receipt download.
- Lets users search by transaction ID or RT number, for example
RT2266088295. Advanced search also filters by external ID, batch ID, status, amount and currency, and labels. Users can filter by preset period or a custom date range, and by transaction type. - Can lock the view to one beneficiary with
beneficiaryHashId, which suits per-contact history pages. - Starts loading data as soon as the iFrame authenticates after
mount(). The iFrame shows its own loading skeleton, so you only need a placeholder until thereadyevent fires.
Live preview
This is the component running against the Nium sandbox with test data. Filter and open transactions to try the history view.
Before you begin
- Complete the Quick start to set up PKCE authorization. Use the scope
['transaction-history-form']when your server requests the authorization code. This scope lets the iFrame read the customer's transaction list and look up status. - Have the
customerHashIdof the signed-in user. The history is scoped to a customer, so your app needs the ID before it mounts this component.
Keep clientSecret and your x-api-key on your server. Use clientHashId on both server and client.
Get a new auth code right before each init() call. An auth code can be used once and expires after its expiresInSecs window, typically 300 seconds.
Quick example
After you call init(), create the transactionHistoryForm element, mount it, and clean up when you're done.
- JavaScript
- React
<div id="txn-history-root"></div>
const { createElement } = await import('@nium/nium-sdk')
const element = await createElement('transactionHistoryForm', {
customerHashId: 'cust-hash-id', // required
prefill: { // optional: pre-populate the filters
dateFrom: '2026-01-01',
dateTo: '2026-06-30',
},
})
element.on('ready', () => hideLoadingSpinner())
element.on('error', (e) => console.error(e.error.code, e.error.message))
// Data loading begins immediately
element.mount('#txn-history-root')
// When you're done, for example on a route change
element.destroy()
import { useEffect } from 'react'
export function TransactionHistory({ customerHashId }: { customerHashId: string }) {
useEffect(() => {
let element: { destroy(): void } | undefined
let cancelled = false
;(async () => {
const { createElement } = await import('@nium/nium-sdk')
element = await createElement('transactionHistoryForm', { customerHashId })
if (cancelled) return element.destroy()
element.on('error', (e) => console.error(e.error.code, e.error.message))
element.mount('#txn-history-root')
})()
return () => {
cancelled = true
element?.destroy()
}
}, [customerHashId])
return <div id="txn-history-root" style={{ minHeight: '500px' }} />
}
In development, React StrictMode runs effects twice. The cancelled flag in the example prevents a double iFrame mount.
Options
createElement('transactionHistoryForm', options) accepts these options (type NiumTransactionHistoryFormOptions). Only customerHashId is required.
| Option | Type | Description |
|---|---|---|
customerHashId | string | Required. Scopes the history to one Nium customer. |
beneficiaryHashId | string | Locks the beneficiary filter to one beneficiary. The user can't change it. |
prefill | NiumTransactionHistoryPrefill | Sets the initial filters on load. The user can change them. See Prefill. |
theme | NiumTheme | Visual customization. See Theming. |
Prefill
All prefill fields are optional.
| Field | Type | Description |
|---|---|---|
transactionId | string | Pre-fills the transaction ID search, for example 'RT2266088295', so the list opens filtered to that transaction. |
dateFrom | string | Start of a custom date range. Format: 'YYYY-MM-DD'. Takes precedence over timePeriod. |
dateTo | string | End of a custom date range. Format: 'YYYY-MM-DD'. |
timePeriod | 'allTime' | 'last30Days' | 'last90Days' | 'last180Days' | Pre-selects a time-period preset. Ignored when dateFrom or dateTo is set. Defaults to all time. |
transactionTypes | NiumTransactionTypeFilter[] | Pre-selects the Transaction type filter with customer-facing labels such as 'Money Sent', 'Money Received', and 'Fee'. Unknown labels are ignored. An empty array means all types. |
Only the top-level beneficiaryHashId locks a filter. Use it to lock the view to one beneficiary. There's no editable beneficiary prefill.
Prefill examples
To support a Track button on your payout confirmation page, pass the transactionId from the payout submit event to open the history filtered to that transaction.
const el = await createElement('transactionHistoryForm', {
customerHashId,
prefill: {
transactionId: 'RT2266088295', // opens filtered to this transaction
dateFrom: '2026-01-01',
dateTo: '2026-12-31',
},
})
To show only the transactions for one beneficiary, lock the filter.
const el = await createElement('transactionHistoryForm', {
customerHashId,
beneficiaryHashId: 'benny-uuid-here', // locked
prefill: {
dateFrom: '2026-01-01', // editable by the user
},
})
Events
Listen to form events with element.on(eventName, handler). Handlers fire asynchronously through postMessage from the iFrame. element.on() returns a function that removes that listener.
ready: The iFrame loaded and connected to the SDK. It then shows its own loading skeleton until transactions arrive. Hide your placeholder here.resize: The content height changed, for example as the list loads or a detail view opens. The SDK resizes the iFrame for you, so listen only if your layout needs to react. Payload:{ height: number }.error: SDK initialization failed, or fetching transaction data failed. Always attach this handler. Payload:{ error: { message: string, code: string } }.
This component has no submit or cancel events.
Common errors and fixes
| Symptom | Cause | Fix |
|---|---|---|
| Auth code expired or invalid | The auth code was already used or is older than its expiresInSecs window, typically 300 seconds. | Get a new auth code right before each init() call. |
| Skeleton rows don't resolve | The customer has no transactions in the current date range. This is the expected empty state. | Widen the date range or create test transactions. |
| Filter changes don't appear | The element doesn't update filters in place. | Reinitialize the element. See Change filters. |
| iFrame mounts but stays blank | The auth call used the wrong scope. | Check that the scopes array has transaction-history-form, not rfi-form. |
Change filters
Users can change filters freely inside the iFrame. The element has no API to change them from your page. To apply new filters from your page, destroy the element, get a new auth code, then call init() and createElement() with updated prefill values. Auth codes are single-use, so call init() again with a fresh code before you create a new element.
'use client'
import { useEffect, useRef } from 'react'
import type { NiumElement } from '@nium/nium-sdk'
export function TransactionHistoryWidget({ customerHashId }: { customerHashId: string }) {
const elementRef = useRef<NiumElement | null>(null)
// Prevents double-mount in React StrictMode
const initializedRef = useRef(false)
const loadHistory = async (txnId?: string) => {
// Tear down the existing element before reinit
elementRef.current?.destroy()
elementRef.current = null
const codeVerifier = generateCodeVerifier()
const codeChallenge = await generateCodeChallengeFromVerifier(codeVerifier)
const { authorizationCode } = await fetch('/api/auth', {
method: 'POST',
body: JSON.stringify({ codeChallenge }),
}).then(r => r.json())
const { init, createElement } = await import('@nium/nium-sdk')
await init({ env: 'production', authCode: authorizationCode, codeVerifier, clientId: CLIENT_ID })
const el = await createElement('transactionHistoryForm', {
customerHashId,
prefill: { transactionId: txnId },
})
el.on('error', (e) => console.error(e.error.message))
elementRef.current = el
el.mount('#txn-history-root')
}
useEffect(() => {
if (!initializedRef.current) {
initializedRef.current = true
loadHistory()
}
return () => {
elementRef.current?.destroy()
elementRef.current = null
}
}, [])
return (
<div>
<button onClick={() => loadHistory()}>Reset</button>
<div id="txn-history-root" style={{ minHeight: '500px' }} />
</div>
)
}
Clean up
Call element.destroy() on page leave or route change. Both destroy() and unmount() remove the iFrame from the DOM and clear all event listeners.
element.destroy(): Removes the iFrame and all listeners permanently. Create a new element to show the history again.element.unmount(): Removes the iFrame and listeners, and keeps the instance. Callmount()again to show it.
Theming
Pass a NiumTheme object in the theme option to control fonts, colors, input styles, and border radius. The Theme customization reference documents all 15 theme properties and 4 ready-to-use example themes.
Next steps
- Send payouts: Let users send payments.
- Respond to RFIs: Let users respond to compliance requests on a transaction.
- Theme customization: Match the component to your brand.