Skip to main content

Webhook Events

This page documents all webhook events that FYATU can send to your endpoint.

Event Categories

Card Events

EventDescription
card.createdA new card has been created
card.fundedFunds added to a card
card.unloadedFunds withdrawn from card
card.frozenCard has been frozen
card.unfrozenCard has been unfrozen
card.terminatedCard has been terminated
card.maintenance_fee_paidMonthly maintenance fee charged

Card Transaction Events

EventDescription
card.transaction.approvedCard payment was approved
card.transaction.declinedCard payment was declined
card.transaction.reversedTransaction was reversed/refunded

Cardholder Events

EventDescription
cardholder.createdA new cardholder has been created
cardholder.updatedCardholder information updated
cardholder.kyc_approvedCardholder KYC verification approved
cardholder.kyc_rejectedCardholder KYC verification rejected
cardholder.suspendedCardholder has been suspended
cardholder.activatedCardholder 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"
  }
}
FieldTypeDescription
eventstringThe event type identifier
versionstringWebhook version (currently 2.0)
signstringHMAC-SHA256 signature for verification
dataobjectEvent-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"
  }
}
FieldTypeDescription
cardIdstringUnique card identifier
cardholderIdstringAssociated cardholder ID
typestringCard type: VIRTUAL or PHYSICAL
brandstringCard brand: VISA or MASTERCARD
currencystringCard currency (usually USD)
last4stringLast 4 digits of card number
statusstringCard 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"
  }
}
FieldTypeDescription
cardIdstringCard identifier
referencestringTransaction batch reference
amountnumberAmount added to card
feenumberFunding fee charged
currencystringCurrency code
cardBalanceBeforenumberCard balance before funding
cardBalanceAfternumberCard balance after funding
statusstringAlways 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"
  }
}
FieldTypeDescription
cardIdstringCard identifier
referencestringTransaction batch reference
amountnumberAmount unloaded from card
feenumberUnloading fee charged
currencystringCurrency code
cardBalanceBeforenumberCard balance before unload
cardBalanceAfternumberCard balance after unload
statusstringAlways 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"
  }
}
FieldTypeDescription
cardIdstringCard identifier
reasonstringReason for freezing
statusstringAlways 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"
  }
}
FieldTypeDescription
cardIdstringCard identifier
statusstringAlways 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"
  }
}
FieldTypeDescription
cardIdstringCard identifier
reasonstringReason for termination
statusstringAlways 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"
  }
}
FieldTypeDescription
cardIdstringCard identifier
amountnumberMaintenance fee amount
currencystringCurrency code
statusstringAlways processed
messagestringDescription 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"
  }
}
FieldTypeDescription
cardIdstringCard identifier
referencestringTransaction reference ID
amountnumberTransaction amount
currencystringCurrency code
merchant.namestringMerchant name
merchant.categorystringMerchant category code
merchant.countrystringMerchant country (ISO code)
cardBalanceBeforenumberBalance before transaction
cardBalanceAfternumberBalance after transaction
statusstringAlways 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"
  }
}
FieldTypeDescription
cardIdstringCard identifier
referencestringTransaction reference ID
amountnumberAttempted transaction amount
currencystringCurrency code
merchant.namestringMerchant name
merchant.categorystringMerchant category
reasonstringDecline reason
statusstringAlways DECLINED

Common Decline Reasons

ReasonDescription
Insufficient fundsCard balance too low
Card frozenCard is temporarily frozen
Card terminatedCard has been terminated
Spending limit exceededMonthly limit reached
Merchant blockedMerchant 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"
  }
}
FieldTypeDescription
cardIdstringCard identifier
referencestringReversal reference ID
originalReferencestringOriginal transaction reference
amountnumberRefunded amount
currencystringCurrency code
statusstringAlways 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"
  }
}
FieldTypeDescription
cardholderIdstringUnique cardholder identifier
firstNamestringCardholder’s first name
lastNamestringCardholder’s last name
emailstringCardholder’s email
phonestringCardholder’s phone (E.164 format)
statusstringCardholder 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"
  }
}
FieldTypeDescription
cardholderIdstringCardholder identifier
changesarrayList of fields that were updated
statusstringCurrent 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"
  }
}
FieldTypeDescription
cardholderIdstringCardholder identifier
firstNamestringCardholder’s first name
lastNamestringCardholder’s last name
kycLevelstringKYC verification level
statusstringAlways 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"
  }
}
FieldTypeDescription
cardholderIdstringCardholder identifier
firstNamestringCardholder’s first name
lastNamestringCardholder’s last name
reasonstringRejection reason
statusstringAlways 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"
  }
}
FieldTypeDescription
cardholderIdstringCardholder identifier
reasonstringSuspension reason
statusstringAlways 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"
  }
}
FieldTypeDescription
cardholderIdstringCardholder identifier
statusstringAlways 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

  1. Respond quickly - Return a 200 OK response within 5 seconds
  2. Process asynchronously - Queue webhook processing for reliability
  3. Verify signatures - Always verify the HMAC signature
  4. Handle duplicates - Use the reference field for idempotency
  5. Log everything - Keep webhook logs for debugging