← Blog & Resources

Mastering Sovereign Series Integration - Webhooks, Sandbox Testing, and Ledger Reconciliation

EN only

Integrating with B2B fintech APIs requires a different mindset than consumer applications. When you are building against the ThisKard Sovereign series—specifica

Published July 5, 2026 · ThisKard team

Integrating with B2B fintech APIs requires a different mindset than consumer applications. When you are building against the ThisKard Sovereign series—specifically CitadelPayroll, VaultSpend, and OnyxClearing—you are dealing with high-value, bulk operations where data integrity is non-negotiable. Unlike our Global series (Flash, Prime, Horizon), which prioritizes user experience for C-end consumers, the Sovereign series prioritizes auditability, reconciliation, and operational security.

For operators and developers, the challenge isn't just making an API call; it’s ensuring that the state of your internal ledger perfectly mirrors the state of the ThisKard platform.

This tutorial will guide you through the lifecycle of a Sovereign transaction, from sandbox simulation to production reconciliation.

Prerequisites

Before we begin, ensure you have the following:

  • A ThisKard Developer Portal account.
  • An active Sandbox Application ID and Secret Key.
  • Node.js (v18+) or Python (v3.9+) installed.
  • Familiarity with REST APIs and HMAC signatures.

Phase 1: The Sovereign Sandbox Environment

The Sovereign sandbox is a distinct environment from the Global series. It simulates complex banking rails and clearing networks that are specific to B2B operations.

Configuration

To interact with the sandbox, you must route requests to the Sovereign-specific base URL.

Base URL: https://api.thiskard.com/v1/sovereign/

When authenticating, you must include the X-Sovereign-Context header. This ensures your request hits the correct logic engine (e.g., payroll vs. clearing).

// Example Configuration Object
const config = {
  baseUrl: 'https://api.thiskard.com/v1/sovereign',
  apiKey: process.env.THISKARD_API_KEY,
  sandboxMode: true,
  context: 'citadel_payroll' // Options: citadel_payroll, vault_spend, onyx_clearing
};

Simulating Events

In the Global series, a user might tap a card, and a webhook fires instantly. In the Sovereign series, specifically with OnyxClearing, transactions often move through ACH or wire simulation cycles that take "T+1" or "T+2" days.

To test these without waiting 48 hours, the Sovereign sandbox provides "Time Jump" and "State Transition" endpoints. You can force a pending transaction to move to settled or failed to test your error handling logic.

Phase 2: Designing the Webhook Pipeline

Webhooks are the nervous system of a Sovereign integration. Polling is inefficient for batch operations like CitadelPayroll, where a single API call might trigger 1,000 individual employee payments. You don't want to poll the status of 1,000 transactions every second.

The Sovereign Webhook Payload

Sovereign webhooks differ from standard consumer webhooks. They are batch-aware.

A typical payout.processed event from CitadelPayroll looks like this:

{
  "id": "evt_sov_9f8a7b6c",
  "object": "event",
  "type": "citadel_payout.batch_processed",
  "api_version": "2023-01-01",
  "data": {
    "batch_id": "batch_12345",
    "total_amount": 5000000,
    "currency": "USD",
    "status": "settled",
    "item_count": 100,
    "summary_url": "https://api.thiskard.com/v1/sovereign/reports/batch_12345"
  },
  "timestamp": 1678900000
}

Verifying the Signature

Security is paramount. Every POST request to your webhook endpoint includes a ThisKard-Signature header. You must validate this HMAC-SHA256 signature to ensure the request originated from ThisKard and not a malicious actor attempting to manipulate your ledger.

Do not skip signature verification in production.

Phase 3: Step-by-Step Integration Workflow

Let's build a robust integration handler using TypeScript/Node.js. We will cover:

  1. Creating a payroll batch in CitadelPayroll.
  2. Handling the resulting webhook.
  3. Reconciling the ledger.

Step 1: Initiating a Sovereign Operation

Here we initiate a payroll batch. We use the idempotency-key header to prevent duplicate payouts if network timeouts occur.

import axios from 'axios';
import crypto from 'crypto';

// Initialize the client
const apiClient = axios.create({
  baseURL: 'https://api.thiskard.com/v1/sovereign',
  headers: {
    'Authorization': `Bearer ${process.env.THISKARD_API_KEY}`,
    'Content-Type': 'application/json'
  }
});

async function initiatePayrollBatch(employees: any[]) {
  try {
    const idempotencyKey = crypto.randomUUID();
    
    const response = await apiClient.post('/citadel/batches', {
      processing_date: '2023-12-01',
      entries: employees.map(emp => ({
        recipient_id: emp.thiskard_id,
        amount: emp.salary,
        description: `Monthly Salary - ${emp.name}`
      }))
    }, {
      headers: {
        'Idempotency-Key': idempotencyKey,
        'X-Sovereign-Context': 'citadel_payroll'
      }
    });

    console.log(`Batch created: ${response.data.batch_id}`);
    return response.data;
  } catch (error) {
    if (axios.isAxiosError(error)) {
      console.error('API Error:', error.response?.data);
    }
    throw error;
  }
}

Step 2: Handling the Webhook

This is the critical operational piece. We will create an Express server to listen for events.

import express, { Request, Response } from 'express';
import crypto from 'crypto';

const app = express();
const PORT = 3000;

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

// The Webhook Endpoint
app.post('/webhooks/sovereign', (req: Request, res: Response) => {
  const signature = req.headers['thiskard-signature'] as string;
  const payload = req.rawBody;
  const secret = process.env.THISKARD_WEBHOOK_SECRET as string;

  // 1. Verify Signature
  const expectedSignature = crypto
    .createHmac('sha256', secret)
    .update(payload)
    .digest('hex');

  if (signature !== expectedSignature) {
    console.error('Invalid signature detected.');
    return res.status(401).send('Invalid signature');
  }

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

  // 3. Handle based on Sovereign Product
  try {
    switch (event.type) {
      case 'citadel_payout.batch_processed':
        reconcileCitadelBatch(event.data);
        break;
      case 'vault_spend.transaction.auth':
        handleVaultSpendAuth(event.data);
        break;
      case 'onyx_clearing.transfer.failed':
        handleClearingFailure(event.data);
        break;
      default:
        console.log(`Unhandled event type: ${event.type}`);
    }

    // Acknowledge receipt to prevent retries
    res.status(200).send('Received');
  } catch (err) {
    console.error('Processing error:', err);
    // Return 500 so ThisKard retries the webhook
    res.status(500).send('Processing Error');
  }
});

function reconcileCitadelBatch(data: any) {
  // Implementation details in Phase 4
  console.log(`Reconciling batch ${data.batch_id} for amount ${data.total_amount}`);
}

app.listen(PORT, () => {
  console.log(`Sovereign Webhook Server running on port ${PORT}`);
});

Phase 4: Ledger Reconciliation Strategies

The most common failure point in B2B integrations is "Ledger Drift." This happens when your internal database believes a transaction happened, but the bank or ThisKard has a different record.

For CitadelPayroll and OnyxClearing, we recommend the Transaction ID State Machine approach.

The State Machine

Every transaction in your database should map to a ThisKard transaction_id. Do not rely solely on amounts.

  1. Initiated: You sent the API request. Store the batch_id.
  2. Pending: ThisKard acknowledged the request (Webhook: batch.acknowledged).
  3. Settled: Money moved (Webhook: batch.processed).
  4. Failed: Money returned or rejected (Webhook: batch.failed).

The Reconciliation Loop

When a webhook hits your reconcileCitadelBatch function, do not just mark everything as "paid." You must fetch the detailed report.

Sovereign batches are atomic operations, but individual items within the batch can fail (e.g., an invalid account number for one employee).

Best Practice: When you receive a batch_processed event, use the summary_url provided in the webhook payload to fetch the granular status of every item in that batch.

# Python pseudo-code for reconciliation logic
import requests

def reconcile_batch(summary_url, internal_batch_id):
    # Fetch the detailed report from ThisKard
    response = requests.get(summary_url, headers={"Authorization": "Bearer ..."})
    report = response.json()

    for item in report['items']:
        local_tx = Database.get_transaction(item['reference_id'])
        
        if item['status'] == 'settled':
            local_tx.mark_settled()
        elif item['status'] == 'failed':
            local_tx.mark_failed(reason=item['error_code'])
            # Trigger alerting for the ops team
            OpsAlert.notify(f"Failed payout for {item['reference_id']}")
        
        local_tx.save()

Handling OnyxClearing Exceptions

OnyxClearing handles inter-company transfers and high-value clearing. These are prone to regulatory holds or manual reviews.

If you receive a onyx_clearing.transfer.held webhook, your reconciliation engine must pause the expected availability date for those funds. Do not automate credit in your ledger until the released event is received.

Error Handling and Best Practices

Integrating with Sovereign products requires defensive coding. Here are the golden rules:

1. Handle 5xx with Exponential Backoff

If api.thiskard.com returns a 500 or 503, do not spam the endpoint. Implement an exponential backoff retry strategy. This prevents your integration from being rate-limited during outages.

2. Idempotency is Mandatory

Always send an Idempotency-Key header with POST