← Blog & Resources

Webhook Integration Guide - Listening for Authorization, Settlement, and KYT Events

EN only

In the world of card issuing, timing is everything. When a user swipes a card, the transaction lifecycle begins milliseconds later, spanning from authorization

Published July 3, 2026 · ThisKard team

In the world of card issuing, timing is everything. When a user swipes a card, the transaction lifecycle begins milliseconds later, spanning from authorization requests to final settlement. While polling APIs might work for end-of-day reconciliation, it fails for real-time use cases like instant notifications, fraud detection, or updating ledger balances.

Webhooks are the nervous system of modern fintech applications. They allow the thiskard platform to push real-time data to your backend whenever a significant event occurs, ensuring your system state mirrors the network state without constant API calls.

This guide focuses on the three most critical event categories you must handle: Authorizations (the request to pay), Settlements (the final transfer of funds), and KYT (Know Your Transaction) events (compliance and risk triggers). We will walk through setting up a secure webhook integration, handling these payloads, and managing edge cases.

Target Audience

This tutorial is designed for backend developers and software architects integrating card issuing capabilities into their applications. We assume familiarity with REST APIs, JSON, and basic server-side programming using Node.js/Express or Python/Flask.

The Transaction Lifecycle: Why These Three Events?

Before writing code, it is vital to understand why we separate these events.

  1. Authorization Events (transaction.authorization): This happens first. A card network asks, "Can this card spend $50 at Merchant X?" You have seconds to respond with an Approval or Decline. This is where you check the user's wallet balance in real-time.
  2. Settlement Events (transaction.settlement): This happens later (hours or days later). The merchant claims the money. The funds actually leave the user's account. This is when you finalize the ledger entry.
  3. KYT Events (compliance.kyt.alert): These occur when our AI detects anomalous behavior (e.g., sudden high-value international transfers). You must listen to these to satisfy regulatory requirements and manage risk.

Prerequisites

To follow along, ensure you have:

  • A developer account on thiskard.com.
  • Your API Secret Key and Signing Secret (found in the Developer Dashboard).
  • Node.js (v14+) or Python (v3.8+) installed.
  • A tunneling service like ngrok to expose your local server for testing.

Step 1: Setting Up Your Endpoint

Your webhook endpoint is a specific URL on your server designated to receive POST requests from api.thiskard.com. For this tutorial, we will build a Node.js server using Express.

First, initialize your project:

mkdir kard-webhooks && cd kard-webhooks
npm init -y
npm install express body-parser crypto

Create an index.js file. We will start with a basic server structure and a dedicated route /webhooks/thiskard.

Security First: Verifying the Signature

In fintech, you cannot trust incoming requests blindly. You must verify that the request actually came from thiskard and not a malicious actor trying to fake a transaction.

thiskard signs every webhook payload with an HMAC-SHA256 signature. You must calculate the hash using your Signing Secret and compare it to the signature provided in the X-ThisKard-Signature header.

Here is the complete, runnable setup:

// index.js
const express = require('express');
const crypto = require('crypto');
const app = express();
const PORT = 3000;

// Ideally, store this in environment variables
const THISKARD_SIGNING_SECRET = process.env.THISKARD_SIGNING_SECRET || 'whsec_test_secret_key';

// Middleware to capture raw body for signature verification
// We need the raw string to verify the hash correctly
app.use(express.json({
    verify: (req, res, buf) => {
        req.rawBody = buf;
    }
}));

// Helper function to verify the webhook signature
const verifySignature = (payload, signature, secret) => {
    const expectedSignature = crypto
        .createHmac('sha256', secret)
        .update(payload)
        .digest('hex');
    
    // Use timing-safe comparison to prevent timing attacks
    return crypto.timingSafeEqual(
        Buffer.from(expectedSignature, 'hex'),
        Buffer.from(signature, 'hex')
    );
};

// The Webhook Endpoint
app.post('/webhooks/thiskard', async (req, res) => {
    const signature = req.headers['x-thiskard-signature'];
    const rawBody = req.rawBody.toString();

    // 1. Verify the request origin
    if (!signature || !verifySignature(rawBody, signature, THISKARD_SIGNING_SECRET)) {
        console.error('Invalid signature. Rejecting request.');
        return res.status(401).json({ error: 'Invalid signature' });
    }

    // 2. Parse the event
    const event = req.body;
    
    console.log(`Received event: ${event.type} | ID: ${event.id}`);

    try {
        // 3. Route the event to the appropriate handler
        switch (event.type) {
            case 'transaction.authorization':
                await handleAuthorization(event.data);
                break;
            
            case 'transaction.settlement':
                await handleSettlement(event.data);
                break;

            case 'compliance.kyt.alert':
                await handleKYTAlert(event.data);
                break;

            default:
                console.log(`Unhandled event type: ${event.type}`);
        }

        // 4. Acknowledge receipt
        // Always return 200 quickly to prevent retries
        res.status(200).json({ received: true });

    } catch (error) {
        console.error(`Error processing event ${event.id}:`, error);
        // Return 500 to signal a processing failure (triggers retry logic)
        res.status(500).json({ error: 'Processing failed' });
    }
});

// --- Event Handlers ---

async function handleAuthorization(data) {
    // Logic: Check user balance and approve/decline conceptually
    // In a real scenario, you might call the decision API
    console.log(`AUTH REQUEST: Card ${data.card_token} attempting $${data.amount} at ${data.merchant_name}`);
    // Simulate checking balance
    if (data.amount <= 500) {
        console.log(`-> Decision: APPROVED`);
    } else {
        console.log(`-> Decision: DECLINED (Insufficient Funds)`);
    }
}

async function handleSettlement(data) {
    // Logic: Finalize transaction in your ledger
    console.log(`SETTLEMENT: Transaction ${data.auth_id} settled for $${data.settlement_amount}`);
    // Update database logic here
    console.log(`-> Ledger updated for user ${data.user_id}.`);
}

async function handleKYTAlert(data) {
    // Logic: Flag user or transaction for review
    console.log(`KYT ALERT: Risk Level ${data.risk_level} detected for Transaction ${data.transaction_id}`);
    if (data.risk_level === 'HIGH') {
        console.log(`-> Action: Account ${data.user_id} flagged for manual review.`);
    }
}

app.listen(PORT, () => {
    console.log(`Webhook server running on port ${PORT}`);
    console.log(`Exposing via ngrok? Use the https URL to register at api.thiskard.com`);
});

Testing Locally

Run your server:

node index.js

In a separate terminal, start ngrok:

ngrok http 3000

Take the HTTPS URL provided by ngrok (e.g., https://abc123.ngrok.io) and append your route: https://abc123.ngrok.io/webhooks/thiskard. This is the URL you will register with our API.


Step 2: Registering the Webhook

Now that you have an endpoint listening, you need to tell api.thiskard.com where to send the events. You can register your webhook via the Dashboard or programmatically via the API.

Here is how you register it using a curl request to our endpoint:

curl -X POST https://api.thiskard.com/v1/webhooks \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "url": "https://abc123.ngrok.io/webhooks/thiskard",
    "events": [
      "transaction.authorization",
      "transaction.settlement",
      "compliance.kyt.alert"
    ]
  }'

Upon success, the API will return a webhook_id and a signing_secret. Copy this signing secret immediately and update your environment variable. Without it, the signature verification in the code above will fail.


Step 3: Handling the Event Workflows

With the infrastructure in place, let's dive into the specific logic required for each event type.

1. Authorization Events (transaction.authorization)

This is the most time-sensitive webhook. When a card is swiped, the network asks for authorization.

The Workflow:

  1. Receive: You get the payload containing amount, currency, merchant_category_code, and card_token.
  2. Check Balance: Look up the user's available balance in your database.
  3. Business Logic: Apply specific rules (e.g., "Is this merchant allowed?", "Does the user have daily limits?").
  4. Respond: If you are using the stand-in processing model, you might need to respond directly. However, typically with thiskard, this webhook is for notification of a decision already made or a request for advice.

Best Practice: Keep your database lookup fast. The card network timeout is usually under 2 seconds. If your server takes too long to respond, the transaction might be declined by the network automatically.

2. Settlement Events (transaction.settlement)

Authorization puts a "hold" on funds. Settlement actually moves them. This is often called "clearing."

The Workflow:

  1. Match: Use the auth_id (provided in the payload) to find the original authorization record in your database.
  2. Reconcile: Sometimes the settlement amount differs slightly from the authorized amount (e.g., a tip added at a restaurant).
  3. Finalize: Deduct the funds from the user's "pending" balance to "posted" balance.

Edge Case: What if you receive a settlement without an authorization? This is rare but can happen with "force posts." Your code should handle creating a transaction record even if the original auth wasn't found.

3. KYT Events (compliance.kyt.alert)

Know Your Transaction (KYT) events are triggered by our compliance engine when transaction patterns match suspicious behavior (e.g., structuring, rapid high-value transfers, or interactions with sanctioned entities).

The Workflow:

  1. Assess Risk: The payload contains a risk_score and risk_reasons.
  2. Action:
    • Low Risk: Log the event for audit trails.
    • Medium Risk: Flag the user for enhanced monitoring.
    • High Risk: Suspend the card immediately via the API to prevent further loss.

Compliance Tip: Do not ignore these webhooks. Regulations often require fintechs to demonstrate a