End-to-end remittance flow
For Remittances API users (IMTOs, MSBs, EMIs). End-to-end walk-through covering: register an end-user → upload KYC → provision a VIBAN → receive an inflow → send an outbound payout.
Gated: requires the Remittances feature flag on your merchant account. Contact support@the-technest.com to enable.
Register the end-user as a customer
When a Pouch user (or your equivalent) signs up, your backend creates a Swappr customer record:
const customer = await fetch('https://api.swappr.me/api/v1/customers', {
method: 'POST',
headers: { /* auth + idempotency */ },
body: JSON.stringify({
type: 'individual',
first_name: pouchUser.firstName,
last_name: pouchUser.lastName,
email: pouchUser.email,
country: 'CA',
id_type: 'passport',
id_number: pouchUser.passportNumber,
dob: pouchUser.dob,
zip_code: pouchUser.postalCode,
tin: pouchUser.sin,
customer_reference: pouchUser.id, // YOUR opaque user ID
interac_email: pouchUser.interacEmail,
}),
}).then((r) => r.json());
// Persist the Swappr customer.id alongside your user record
await db.users.update({
where: { id: pouchUser.id },
data: { swapprCustomerId: customer.id },
});Status starts at pending — KYC docs come next.
Upload KYC documents
For each required document (ID front, ID back, proof of address, etc.):
// 1. Request upload URL
const intent = await fetch(
`https://api.swappr.me/api/v1/customers/${customer.id}/files`,
{
method: 'POST',
headers: { /* ... */ },
body: JSON.stringify({
file_category: 'id_document',
filename: 'passport.jpg',
content_type: 'image/jpeg',
}),
},
).then((r) => r.json());
// 2. PUT bytes to the upload URL
await fetch(intent.upload_url, {
method: 'PUT',
headers: { 'Content-Type': 'image/jpeg' },
body: passportFileBytes,
});
// 3. Track the file_id for the attach step
fileIds.push({ slot: 'id_document', file_id: intent.file_id });Attach files + submit KYC
Once all docs are uploaded, attach them in named slots + submit:
await fetch(
`https://api.swappr.me/api/v1/customers/${customer.id}/files/attach`,
{
method: 'POST',
headers: { /* ... */ },
body: JSON.stringify({
id_file: fileIds.find((f) => f.slot === 'id_document').file_id,
proof_of_address_file: fileIds.find((f) => f.slot === 'proof_of_address').file_id,
}),
},
);
// Submit for verification review
await fetch(`https://api.swappr.me/api/v1/customers/${customer.id}/kyc`, {
method: 'POST',
headers: { /* ... */ },
body: '{}',
});Listen for verification
Subscribe to customer_verified + customer_rejected webhooks:
async function handleCustomerVerified(data: any) {
await db.users.update({
where: { swapprCustomerId: data.id },
data: {
kycStatus: 'verified',
kycVerifiedAt: new Date(data.verified_at),
},
});
// Now provision a VIBAN if you want to receive money
await provisionVibanForUser(data.id);
}
async function handleCustomerRejected(data: any) {
await db.users.update({
where: { swapprCustomerId: data.id },
data: {
kycStatus: 'rejected',
kycRejectionReason: data.rejection_reason,
},
});
// Notify user, prompt them to retry with a NEW customer_reference
}Typical verification time: a few hours to 1 business day.
Provision a VIBAN
After verification, create a per-customer VIBAN to receive money in GBP / USD / EUR:
const viban = await fetch(
`https://api.swappr.me/api/v1/customers/${customerId}/virtual_accounts`,
{
method: 'POST',
headers: { /* ... */ },
body: JSON.stringify({ currency: 'GBP' }),
},
).then((r) => r.json());
// Display in your UI
console.log({
account_number: viban.account_number,
bank_name: viban.bank_name,
// For GBP: also include sort_code in the bank_code field
});If account_number is empty, the VIBAN is provisioning — wait for the virtual_account_activated webhook before showing it to the user.
For CAD, don’t create a VIBAN — set interac_email at customer-create time and the upstream FX rail routes inbound Interac transfers automatically.
Receive an inflow
When money lands at the customer’s VIBAN, Swappr fires wallet_funded with a customer block identifying which end-user the inflow belongs to. Sample sender values below are fictional — real deliveries carry the actual sender’s bank-of-record name.
async function handleWalletFunded(event: any) {
// Match the inflow to your end-user via the customer block
const user = await db.users.findUnique({
where: { swapprCustomerId: event.customer.id },
});
// Credit the user's app-side balance + log the transaction.
// Example sender values: "EMILY HARRIET ASHWORTH" / "87654321" /
// "203000" — your handler sees the actual remitter's details on
// each delivery.
await db.transactions.create({
data: {
userId: user.id,
type: 'inflow',
currency: event.currency,
amountMinor: BigInt(event.amountMinor),
senderName: event.sender?.name,
senderAccountNumber: event.sender?.accountNumber,
senderBankName: event.sender?.bankName,
narration: event.narration,
swapprLedgerEntryId: event.ledgerEntryId,
receivedAt: new Date(event.receivedAt),
},
});
// Notify the user — push, email, SMS
await sendUserNotification(
user,
`You received ${formatMoney(event.amountMinor, event.currency)}`,
);
}The merchant wallet (your business wallet) is credited at Swappr; per-user accounting on your app side is your responsibility.
Send an outbound payout
When the user wants to send money to a recipient, submit a payout WITH the sender_customer_id so Swappr attaches the sender identity per-transaction (CBN IMTO compliance):
await fetch('https://api.swappr.me/api/v1/payouts', {
method: 'POST',
headers: {
'Authorization': `Bearer ${SWAPPR_API_KEY}`,
'Idempotency-Key': randomUUID(),
'Content-Type': 'application/json',
},
body: JSON.stringify({
amount_minor: '500000',
currency: 'NGN',
recipient: {
account_number: '0690000032',
bank_code: '044',
},
sender_customer_id: user.swapprCustomerId, // Auto-resolves sender info
merchant_reference: `POUCH_OUT_${user.id}_${transactionId}`,
}),
});Sender info is auto-resolved from the customer’s KYC fields (first/last name, ID type, ID number, DOB, ID expiry) — no need to duplicate them in every payout call.
Track outbound completion
Same as the Universal API webhooks flow — payout_paid / payout_failed events fire on completion. Update the user’s transaction record + notify them.
Architecture diagram
┌─────────────┐ ┌────────────────────┐ ┌──────────┐
│ Your app │────────▶│ Your backend │────────▶│ Swappr │
│ (end-user) │ │ (your servers) │ │ API │
└─────────────┘ └────────────────────┘ └──────────┘
│
▼
┌──────────┐
│ Upstream │
│ FX + │
│ KYC │
│ rails │
└──────────┘
◀────── webhooks ─────────────┘
(customer_verified, wallet_funded,
payout_paid, payout_failed)Your app never speaks to upstream rails directly. Your backend talks to Swappr; Swappr proxies to the underlying rails + persists the customer mapping internally. End-user PII is encrypted at rest in Swappr’s DB; only the regulated entity (Technest) has decrypt access.
Compliance notes
- Each end-user payout carries sender identity — required by CBN’s IMTO regulations + most international AML regimes
- KYC is performed by a regulated partner as a sub-processor under Technest’s IMTO licence
- Dual-control on remittance corridors — your team can require maker-checker on payouts above thresholds via dashboard settings
- Remittance reports available via
GET /v1/reports/payouts— filter by date + currency for regulator submissions
For more on regulator filings + audit trails, contact compliance@the-technest.com.