Skip to main content

Documentation Index

Fetch the complete documentation index at: https://docs.fyatu.com/llms.txt

Use this file to discover all available pages before exploring further.

Fires synchronously when a cardholder uses a JIT-enabled card. Fyatu forwards the authorization request from the card network to your registered endpoint and waits up to 1 second for your APPROVE or DECLINE response. Your decision is then forwarded to the card network. The cardholder experiences no perceptible delay.
This event requires a response within 1 second. If your endpoint does not respond in time, Fyatu automatically approves the transaction (provided your program balance is sufficient). Design your handler to be fast — do not call slow external services in the critical path.
You receive this event only for JIT cards (isJitfEnabled: true). Pre-funded cards do not go through your authorization handler — they draw from the card’s own balance without consulting your server.

Event Type

CARD_AUTHORIZATION_VERIFY

Registering a Webhook Endpoint

Register a webhook endpoint subscribed to CARD_AUTHORIZATION_VERIFY via the CaaS Portal under Developers → Webhooks, or via the API:
curl -X POST https://api.fyatu.com/api/v3.20/webhooks \
  -H "Authorization: Bearer $FYATU_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "url":         "https://your-server.com/fyatu/authorization",
    "events":      ["CARD_AUTHORIZATION_VERIFY"],
    "environment": "LIVE"
  }'
Only the first CARD_AUTHORIZATION_VERIFY endpoint registered for your business is used per authorization — register one dedicated, fast endpoint for this event.

Payload

{
  "event":      "CARD_AUTHORIZATION_VERIFY",
  "eventId":    "evt_01HXYZ987654FEDCBA",
  "businessId": "BUS1A2B3C4D5E6F",
  "timestamp":  "2026-05-27T14:32:00Z",
  "data": {
    "cardId":          "crd_01HXYZ5555ABCDEF1111",
    "cardholderId":    "chl_01HXYZ1234ABCDEF5678",
    "amount":          42.50,
    "feeAmount":       1.25,
    "currency":        "USD",
    "merchantName":    "Amazon",
    "merchantMcc":     "5999",
    "merchantCountry": "US",
    "timestamp":       "2026-05-27T14:32:01Z"
  }
}

Payload Fields

FieldTypeDescription
cardIdstringThe card being used
cardholderIdstringThe cardholder who holds the card
amountnumberAuthorization amount in dollars
feeAmountnumberNetwork or cross-border fee in dollars (may be 0.00)
currencystringAuthorization currency (ISO 4217)
merchantNamestringMerchant name from the card network
merchantMccstringMerchant Category Code (ISO 18245). Empty string if not provided by the network
merchantCountrystringTwo-letter country code where the merchant is located. Empty string if not provided
timestampstringISO 8601 timestamp of the authorization request
The total charge to your program ledger on approval will be amount + feeAmount. When evaluating whether to approve, check your program balance against the combined total — not just amount.

Your Response

Respond with HTTP 200 and a JSON body. Both fields are required.

Approve

{
  "decision": "APPROVE"
}

Decline

{
  "decision": "DECLINE",
  "reason":   "VELOCITY_EXCEED"
}

Response Fields

FieldTypeRequiredDescription
decisionstringYes"APPROVE" or "DECLINE"
reasonstringNoDecline reason code. Ignored when decision is "APPROVE"

Decline Reason Codes

CodeWhen to use
VELOCITY_EXCEEDYour program balance is insufficient
INVALID_MERCHANTMerchant is not permitted for this program
BLK_MRCHMerchant is on your blocked list
TXN_NOT_PERMITThis transaction type is not allowed
SUSPECT_FRAUDTransaction flagged as suspicious
RESTRICTEDCard is suspended or access is restricted
CASH_REQ_EXCEEDCash withdrawal limit exceeded
DO_NOT_HONOURGeneric decline — use when no specific code applies

What Happens After Your Response

Your server → APPROVE


Fyatu → HostFi → card network: transaction proceeds


CARD_AUTHORIZATION fires (decision: APPROVED)


TRANSACTION_AUTHORIZED fires (funds reserved)

   ... later ...


TRANSACTION_CLEARED fires (transaction settled)

──────────────────────────────────────────────────

Your server → DECLINE


Fyatu → HostFi → card network: transaction blocked


CARD_AUTHORIZATION fires (decision: DECLINED)
After Fyatu receives your APPROVE, it reserves the funds from your program ledger and responds to the card network. You will later receive TRANSACTION_AUTHORIZED when the authorization is confirmed, and TRANSACTION_CLEARED when the transaction settles.

Timeout and Fallback Behaviour

ScenarioFyatu’s action
Your endpoint responds {"decision": "APPROVE"} within 1 secondTransaction approved
Your endpoint responds {"decision": "DECLINE", ...} within 1 secondTransaction declined
No CARD_AUTHORIZATION_VERIFY endpoint registeredAuto-approve (balance check only)
Endpoint does not respond within 1 secondAuto-approve (fail open)
Endpoint returns non-2xx HTTP statusAuto-approve (fail open)
Response body cannot be parsedAuto-approve (fail open)
Failing open is intentional. An unexpected approval is recoverable — your program ledger is debited and you can investigate. An unexpected decline silently blocks a cardholder at the terminal and is not recoverable. If you need guaranteed blocking behaviour for a card (e.g. a terminated or suspended cardholder), use the card lifecycle endpoints (freeze, terminate) rather than relying solely on the authorization webhook.

Verifying the Signature

All CARD_AUTHORIZATION_VERIFY requests are signed with the same HMAC-SHA256 mechanism as all other Fyatu webhooks. Verify the X-Fyatu-Signature header before processing:
import hmac, hashlib, time

def verify_fyatu_signature(payload_bytes, signature_header, secret):
    # signature_header = "t=<timestamp>,v1=<hex>"
    parts = dict(p.split("=", 1) for p in signature_header.split(","))
    ts = parts.get("t", "")
    received = parts.get("v1", "")

    # Reject if timestamp is more than 5 minutes old
    if abs(time.time() - int(ts)) > 300:
        return False

    signed = f"{ts}.{payload_bytes.decode()}"
    expected = hmac.new(secret.encode(), signed.encode(), hashlib.sha256).hexdigest()
    return hmac.compare_digest(expected, received)
For the full signature verification guide see Signature Verification.

Example Handler

import json
from flask import Flask, request, jsonify, abort

app = Flask(__name__)
WEBHOOK_SECRET = "whsec_your_secret_here"

@app.route("/fyatu/authorization", methods=["POST"])
def authorize():
    sig = request.headers.get("X-Fyatu-Signature", "")
    if not verify_fyatu_signature(request.data, sig, WEBHOOK_SECRET):
        abort(401)

    event = request.get_json()
    if event.get("event") != "CARD_AUTHORIZATION_VERIFY":
        return jsonify({"decision": "APPROVE"})

    data = event["data"]
    card_id       = data["cardId"]
    amount        = data["amount"]
    fee_amount    = data.get("feeAmount", 0)
    merchant_mcc  = data.get("merchantMcc", "")
    total_charge  = amount + fee_amount

    # Example: block gambling MCCs
    BLOCKED_MCCS = {"7995", "7994", "7993"}
    if merchant_mcc in BLOCKED_MCCS:
        return jsonify({"decision": "DECLINE", "reason": "INVALID_MERCHANT"})

    # Example: check your own per-card spending limit
    if exceeds_card_limit(card_id, total_charge):
        return jsonify({"decision": "DECLINE", "reason": "VELOCITY_EXCEED"})

    return jsonify({"decision": "APPROVE"})
Node.js
import express from 'express'
import crypto from 'crypto'

const app = express()
app.use(express.raw({ type: 'application/json' }))

const WEBHOOK_SECRET = process.env.FYATU_WEBHOOK_SECRET!

function verifySignature(payload: Buffer, header: string): boolean {
  const parts = Object.fromEntries(header.split(',').map(p => p.split('=')))
  const ts = parts['t']
  const received = parts['v1']
  if (Math.abs(Date.now() / 1000 - Number(ts)) > 300) return false
  const signed = `${ts}.${payload.toString()}`
  const expected = crypto.createHmac('sha256', WEBHOOK_SECRET).update(signed).digest('hex')
  return crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(received))
}

app.post('/fyatu/authorization', (req, res) => {
  const sig = req.headers['x-fyatu-signature'] as string
  if (!verifySignature(req.body, sig)) return res.status(401).send()

  const event = JSON.parse(req.body.toString())
  if (event.event !== 'CARD_AUTHORIZATION_VERIFY') return res.json({ decision: 'APPROVE' })

  const { cardId, amount, feeAmount = 0, merchantMcc } = event.data
  const total = amount + feeAmount

  const BLOCKED_MCCS = new Set(['7995', '7994', '7993'])
  if (BLOCKED_MCCS.has(merchantMcc)) {
    return res.json({ decision: 'DECLINE', reason: 'INVALID_MERCHANT' })
  }

  if (exceedsCardLimit(cardId, total)) {
    return res.json({ decision: 'DECLINE', reason: 'VELOCITY_EXCEED' })
  }

  return res.json({ decision: 'APPROVE' })
})

EventWhen it fires
CARD_AUTHORIZATIONAfter every authorization decision — informational record of what was approved/declined
TRANSACTION_AUTHORIZEDAuthorization confirmed by the card network
TRANSACTION_CLEAREDTransaction fully settled
TRANSACTION_DECLINEDAuthorization was declined (by you or by Fyatu)
{
  "event":      "CARD_AUTHORIZATION_VERIFY",
  "eventId":    "evt_01HXYZ987654FEDCBA",
  "businessId": "BUS1A2B3C4D5E6F",
  "timestamp":  "2026-05-27T14:32:00Z",
  "data": {
    "cardId":          "crd_01HXYZ5555ABCDEF1111",
    "cardholderId":    "chl_01HXYZ1234ABCDEF5678",
    "amount":          42.50,
    "feeAmount":       1.25,
    "currency":        "USD",
    "merchantName":    "Amazon",
    "merchantMcc":     "5999",
    "merchantCountry": "US",
    "timestamp":       "2026-05-27T14:32:01Z"
  }
}
{
  "decision": "APPROVE"
}