Webhook Events
This page documents all webhook events that FYATU can send to your endpoint.
Event Categories
Card Events
| Event | Description |
|---|
card.created | A new card has been created |
card.funded | Funds added to a card |
card.unloaded | Funds withdrawn from card |
card.frozen | Card has been frozen |
card.unfrozen | Card has been unfrozen |
card.terminated | Card has been terminated |
card.maintenance_fee_paid | Monthly maintenance fee charged |
Card Transaction Events
| Event | Description |
|---|
card.transaction.approved | Card payment was approved |
card.transaction.declined | Card payment was declined |
card.transaction.reversed | Transaction was reversed/refunded |
Cardholder Events
| Event | Description |
|---|
cardholder.created | A new cardholder has been created |
cardholder.updated | Cardholder information updated |
cardholder.kyc_approved | Cardholder KYC verification approved |
cardholder.kyc_rejected | Cardholder KYC verification rejected |
cardholder.suspended | Cardholder has been suspended |
cardholder.activated | Cardholder has been activated |
Webhook Payload Structure
All webhooks follow this standard structure:
{
"event": "event.type",
"version": "2.0",
"sign": "hmac_sha256_signature",
"data": {
// Event-specific data
"appId": "YOUR_APP_ID",
"timestamp": "2026-01-12T18:30:00+00:00"
}
}
| Field | Type | Description |
|---|
event | string | The event type identifier |
version | string | Webhook version (currently 2.0) |
sign | string | HMAC-SHA256 signature for verification |
data | object | Event-specific payload data |
Card Events
card.created
Sent when a new card is created for a cardholder.
{
"event": "card.created",
"version": "2.0",
"sign": "a1b2c3d4e5f6...",
"data": {
"cardId": "CRD678A3B4C5D6E7",
"cardholderId": "ch_abc123xyz",
"type": "VIRTUAL",
"brand": "MASTERCARD",
"currency": "USD",
"last4": "4532",
"status": "ACTIVE",
"appId": "D0H6R7Z6R1C2N5O5",
"timestamp": "2026-01-12T18:30:00+00:00"
}
}
| Field | Type | Description |
|---|
cardId | string | Unique card identifier |
cardholderId | string | Associated cardholder ID |
type | string | Card type: VIRTUAL or PHYSICAL |
brand | string | Card brand: VISA or MASTERCARD |
currency | string | Card currency (usually USD) |
last4 | string | Last 4 digits of card number |
status | string | Card status: ACTIVE |
card.funded
Sent when funds are successfully added to a card.
{
"event": "card.funded",
"version": "2.0",
"sign": "a1b2c3d4e5f6...",
"data": {
"cardId": "CRD678A3B4C5D6E7",
"reference": "FND678A3B4C5D6E8",
"amount": 100.00,
"fee": 1.50,
"currency": "USD",
"cardBalanceBefore": 50.00,
"cardBalanceAfter": 150.00,
"status": "SUCCESS",
"appId": "D0H6R7Z6R1C2N5O5",
"timestamp": "2026-01-12T18:30:00+00:00"
}
}
| Field | Type | Description |
|---|
cardId | string | Card identifier |
reference | string | Transaction batch reference |
amount | number | Amount added to card |
fee | number | Funding fee charged |
currency | string | Currency code |
cardBalanceBefore | number | Card balance before funding |
cardBalanceAfter | number | Card balance after funding |
status | string | Always SUCCESS |
card.unloaded
Sent when funds are withdrawn from a card back to the business wallet.
{
"event": "card.unloaded",
"version": "2.0",
"sign": "a1b2c3d4e5f6...",
"data": {
"cardId": "CRD678A3B4C5D6E7",
"reference": "UNL678A3B4C5D6E9",
"amount": 50.00,
"fee": 0.00,
"currency": "USD",
"cardBalanceBefore": 150.00,
"cardBalanceAfter": 100.00,
"status": "SUCCESS",
"appId": "D0H6R7Z6R1C2N5O5",
"timestamp": "2026-01-12T18:30:00+00:00"
}
}
| Field | Type | Description |
|---|
cardId | string | Card identifier |
reference | string | Transaction batch reference |
amount | number | Amount unloaded from card |
fee | number | Unloading fee charged |
currency | string | Currency code |
cardBalanceBefore | number | Card balance before unload |
cardBalanceAfter | number | Card balance after unload |
status | string | Always SUCCESS |
card.frozen
Sent when a card is frozen (temporarily disabled).
{
"event": "card.frozen",
"version": "2.0",
"sign": "a1b2c3d4e5f6...",
"data": {
"cardId": "CRD678A3B4C5D6E7",
"reason": "Frozen via Business Console",
"status": "FROZEN",
"appId": "D0H6R7Z6R1C2N5O5",
"timestamp": "2026-01-12T18:30:00+00:00"
}
}
| Field | Type | Description |
|---|
cardId | string | Card identifier |
reason | string | Reason for freezing |
status | string | Always FROZEN |
card.unfrozen
Sent when a frozen card is unfrozen (re-activated).
{
"event": "card.unfrozen",
"version": "2.0",
"sign": "a1b2c3d4e5f6...",
"data": {
"cardId": "CRD678A3B4C5D6E7",
"status": "ACTIVE",
"appId": "D0H6R7Z6R1C2N5O5",
"timestamp": "2026-01-12T18:30:00+00:00"
}
}
| Field | Type | Description |
|---|
cardId | string | Card identifier |
status | string | Always ACTIVE |
card.terminated
Sent when a card is permanently terminated.
{
"event": "card.terminated",
"version": "2.0",
"sign": "a1b2c3d4e5f6...",
"data": {
"cardId": "CRD678A3B4C5D6E7",
"reason": "Terminated via API",
"status": "TERMINATED",
"appId": "D0H6R7Z6R1C2N5O5",
"timestamp": "2026-01-12T18:30:00+00:00"
}
}
| Field | Type | Description |
|---|
cardId | string | Card identifier |
reason | string | Reason for termination |
status | string | Always TERMINATED |
Card termination is irreversible. Any remaining balance will be returned to your business wallet.
card.maintenance_fee_paid
Sent when monthly maintenance fee is charged to a card.
{
"event": "card.maintenance_fee_paid",
"version": "2.0",
"sign": "a1b2c3d4e5f6...",
"data": {
"cardId": "CRD678A3B4C5D6E7",
"amount": 2.00,
"currency": "USD",
"status": "processed",
"message": "Monthly card maintenance fee debited",
"appId": "D0H6R7Z6R1C2N5O5",
"timestamp": "2026-01-12T00:00:00+00:00"
}
}
| Field | Type | Description |
|---|
cardId | string | Card identifier |
amount | number | Maintenance fee amount |
currency | string | Currency code |
status | string | Always processed |
message | string | Description of the fee |
Maintenance fees are charged on the first of each month. Ensure cards have sufficient balance to avoid issues.
Card Transaction Events
card.transaction.approved
Sent when a card transaction is successfully approved.
{
"event": "card.transaction.approved",
"version": "2.0",
"sign": "a1b2c3d4e5f6...",
"data": {
"cardId": "CRD678A3B4C5D6E7",
"reference": "TXN550e8400e29b41d4",
"amount": 49.99,
"currency": "USD",
"merchant": {
"name": "AMAZON *MARKETPLACE",
"category": "SHOPPING",
"country": "US"
},
"cardBalanceBefore": 150.00,
"cardBalanceAfter": 100.01,
"status": "APPROVED",
"appId": "D0H6R7Z6R1C2N5O5",
"timestamp": "2026-01-12T18:30:00+00:00"
}
}
| Field | Type | Description |
|---|
cardId | string | Card identifier |
reference | string | Transaction reference ID |
amount | number | Transaction amount |
currency | string | Currency code |
merchant.name | string | Merchant name |
merchant.category | string | Merchant category code |
merchant.country | string | Merchant country (ISO code) |
cardBalanceBefore | number | Balance before transaction |
cardBalanceAfter | number | Balance after transaction |
status | string | Always APPROVED |
card.transaction.declined
Sent when a card transaction is declined.
{
"event": "card.transaction.declined",
"version": "2.0",
"sign": "a1b2c3d4e5f6...",
"data": {
"cardId": "CRD678A3B4C5D6E7",
"reference": "TXN550e8400e29b41d4",
"amount": 299.99,
"currency": "USD",
"merchant": {
"name": "BESTBUY",
"category": "ELECTRONICS"
},
"reason": "Insufficient funds",
"status": "DECLINED",
"appId": "D0H6R7Z6R1C2N5O5",
"timestamp": "2026-01-12T18:30:00+00:00"
}
}
| Field | Type | Description |
|---|
cardId | string | Card identifier |
reference | string | Transaction reference ID |
amount | number | Attempted transaction amount |
currency | string | Currency code |
merchant.name | string | Merchant name |
merchant.category | string | Merchant category |
reason | string | Decline reason |
status | string | Always DECLINED |
Common Decline Reasons
| Reason | Description |
|---|
Insufficient funds | Card balance too low |
Card frozen | Card is temporarily frozen |
Card terminated | Card has been terminated |
Spending limit exceeded | Monthly limit reached |
Merchant blocked | Merchant category restricted |
card.transaction.reversed
Sent when a transaction is reversed (refunded) by the merchant.
{
"event": "card.transaction.reversed",
"version": "2.0",
"sign": "a1b2c3d4e5f6...",
"data": {
"cardId": "CRD678A3B4C5D6E7",
"reference": "REV550e8400e29b41d4",
"originalReference": "TXN550e8400e29b41d4",
"amount": 49.99,
"currency": "USD",
"status": "REVERSED",
"appId": "D0H6R7Z6R1C2N5O5",
"timestamp": "2026-01-12T18:30:00+00:00"
}
}
| Field | Type | Description |
|---|
cardId | string | Card identifier |
reference | string | Reversal reference ID |
originalReference | string | Original transaction reference |
amount | number | Refunded amount |
currency | string | Currency code |
status | string | Always REVERSED |
Reversals credit the amount back to the card balance. Use this to update your records.
Cardholder Events
cardholder.created
Sent when a new cardholder is created.
{
"event": "cardholder.created",
"version": "2.0",
"sign": "a1b2c3d4e5f6...",
"data": {
"cardholderId": "ch_abc123xyz789",
"firstName": "John",
"lastName": "Doe",
"email": "[email protected]",
"phone": "+14155551234",
"status": "ACTIVE",
"appId": "D0H6R7Z6R1C2N5O5",
"timestamp": "2026-01-12T18:30:00+00:00"
}
}
| Field | Type | Description |
|---|
cardholderId | string | Unique cardholder identifier |
firstName | string | Cardholder’s first name |
lastName | string | Cardholder’s last name |
email | string | Cardholder’s email |
phone | string | Cardholder’s phone (E.164 format) |
status | string | Cardholder status |
cardholder.updated
Sent when cardholder information is updated.
{
"event": "cardholder.updated",
"version": "2.0",
"sign": "a1b2c3d4e5f6...",
"data": {
"cardholderId": "ch_abc123xyz789",
"changes": ["email", "phone", "address"],
"status": "ACTIVE",
"appId": "D0H6R7Z6R1C2N5O5",
"timestamp": "2026-01-12T18:30:00+00:00"
}
}
| Field | Type | Description |
|---|
cardholderId | string | Cardholder identifier |
changes | array | List of fields that were updated |
status | string | Current cardholder status |
cardholder.kyc_approved
Sent when a cardholder’s KYC verification is approved.
{
"event": "cardholder.kyc_approved",
"version": "2.0",
"sign": "a1b2c3d4e5f6...",
"data": {
"cardholderId": "ch_abc123xyz789",
"firstName": "John",
"lastName": "Doe",
"kycLevel": "VERIFIED",
"status": "ACTIVE",
"appId": "D0H6R7Z6R1C2N5O5",
"timestamp": "2026-01-12T18:30:00+00:00"
}
}
| Field | Type | Description |
|---|
cardholderId | string | Cardholder identifier |
firstName | string | Cardholder’s first name |
lastName | string | Cardholder’s last name |
kycLevel | string | KYC verification level |
status | string | Always ACTIVE |
cardholder.kyc_rejected
Sent when a cardholder’s KYC verification is rejected.
{
"event": "cardholder.kyc_rejected",
"version": "2.0",
"sign": "a1b2c3d4e5f6...",
"data": {
"cardholderId": "ch_abc123xyz789",
"firstName": "John",
"lastName": "Doe",
"reason": "Document expired",
"status": "REJECTED",
"appId": "D0H6R7Z6R1C2N5O5",
"timestamp": "2026-01-12T18:30:00+00:00"
}
}
| Field | Type | Description |
|---|
cardholderId | string | Cardholder identifier |
firstName | string | Cardholder’s first name |
lastName | string | Cardholder’s last name |
reason | string | Rejection reason |
status | string | Always REJECTED |
cardholder.suspended
Sent when a cardholder is suspended.
{
"event": "cardholder.suspended",
"version": "2.0",
"sign": "a1b2c3d4e5f6...",
"data": {
"cardholderId": "ch_abc123xyz789",
"reason": "Suspicious activity detected",
"status": "SUSPENDED",
"appId": "D0H6R7Z6R1C2N5O5",
"timestamp": "2026-01-12T18:30:00+00:00"
}
}
| Field | Type | Description |
|---|
cardholderId | string | Cardholder identifier |
reason | string | Suspension reason |
status | string | Always SUSPENDED |
cardholder.activated
Sent when a suspended cardholder is reactivated.
{
"event": "cardholder.activated",
"version": "2.0",
"sign": "a1b2c3d4e5f6...",
"data": {
"cardholderId": "ch_abc123xyz789",
"status": "ACTIVE",
"appId": "D0H6R7Z6R1C2N5O5",
"timestamp": "2026-01-12T18:30:00+00:00"
}
}
| Field | Type | Description |
|---|
cardholderId | string | Cardholder identifier |
status | string | Always ACTIVE |
Verifying Webhook Signatures
All webhooks include an HMAC-SHA256 signature in the sign field. Verify it using your webhook secret:
const crypto = require('crypto');
function verifyWebhookSignature(payload, signature, secret) {
const expectedSignature = crypto
.createHmac('sha256', secret)
.update(JSON.stringify(payload.data))
.digest('hex');
return signature === expectedSignature;
}
// In your webhook handler
app.post('/webhooks/fyatu', (req, res) => {
const { event, sign, data } = req.body;
if (!verifyWebhookSignature(req.body, sign, process.env.WEBHOOK_SECRET)) {
return res.status(401).send('Invalid signature');
}
// Process webhook...
res.status(200).send('OK');
});
Complete Handler Example
const crypto = require('crypto');
app.post('/webhooks/fyatu', (req, res) => {
const { event, sign, data } = req.body;
// Verify signature
const expectedSign = crypto
.createHmac('sha256', process.env.WEBHOOK_SECRET)
.update(JSON.stringify(data))
.digest('hex');
if (sign !== expectedSign) {
return res.status(401).send('Invalid signature');
}
// Acknowledge immediately
res.status(200).send('OK');
// Route to handler
switch (event) {
// Card events
case 'card.created':
handleCardCreated(data);
break;
case 'card.funded':
handleCardFunded(data);
break;
case 'card.unloaded':
handleCardUnloaded(data);
break;
case 'card.frozen':
handleCardFrozen(data);
break;
case 'card.unfrozen':
handleCardUnfrozen(data);
break;
case 'card.terminated':
handleCardTerminated(data);
break;
case 'card.maintenance_fee_paid':
handleMaintenanceFee(data);
break;
// Transaction events
case 'card.transaction.approved':
handleTransactionApproved(data);
break;
case 'card.transaction.declined':
handleTransactionDeclined(data);
break;
case 'card.transaction.reversed':
handleTransactionReversed(data);
break;
// Cardholder events
case 'cardholder.created':
handleCardholderCreated(data);
break;
case 'cardholder.updated':
handleCardholderUpdated(data);
break;
case 'cardholder.kyc_approved':
handleKycApproved(data);
break;
case 'cardholder.kyc_rejected':
handleKycRejected(data);
break;
case 'cardholder.suspended':
handleCardholderSuspended(data);
break;
case 'cardholder.activated':
handleCardholderActivated(data);
break;
default:
console.log('Unknown event:', event);
}
});
Best Practices
- Respond quickly - Return a
200 OK response within 5 seconds
- Process asynchronously - Queue webhook processing for reliability
- Verify signatures - Always verify the HMAC signature
- Handle duplicates - Use the
reference field for idempotency
- Log everything - Keep webhook logs for debugging