← Blog & Resources

Sandbox testing workflow - Simulating card issuance, top-ups, and webhooks before production

EN only

In the world of fintech, "move fast and break things" is a dangerous philosophy. When you are dealing with people's money, card networks, and regulatory complia

Published July 3, 2026 · ThisKard team

In the world of fintech, "move fast and break things" is a dangerous philosophy. When you are dealing with people's money, card networks, and regulatory compliance, breaking things in production isn't just an inconvenience—it’s a liability. This is why a robust sandbox environment isn't just a nice-to-have; it is the bedrock of a successful card issuance integration.

If you are building an application using the Thiskard API, your journey from zero to live involves a critical phase: the Sandbox. This is where you validate your logic, test edge cases, and ensure your system handles money movement with the precision of a Swiss watchmaker.

In this tutorial, we will walk through a comprehensive sandbox testing workflow. We won't just cover the happy path; we will simulate card creation, fund wallets via simulated top-ups, and verify that your application listens to webhooks correctly. By the end of this guide, you will have a repeatable testing framework that gives you the confidence to flip the switch to production.

Prerequisites

Before we dive into the code, ensure you have the following:

  1. A Thiskard Developer Account: Sign up at the developer portal to access your dashboard.
  2. API Keys: Navigate to the "API Keys" section. You will need your Test Secret Key. (Note: Test keys start with sk_test_, while Live keys start with sk_live_).
  3. Node.js Environment: We will use JavaScript (Node.js) for this tutorial. Ensure you have Node.js v14 or higher installed.
  4. A Webhook Listener: For testing webhooks locally, we recommend using a tunneling service like ngrok combined with a simple Express server, or a dedicated tool like the Thiskard CLI webhook forwarder.

Step 1: Configuring Your Environment

The first rule of fintech development is: Never hardcode API keys. Let’s set up a local environment to keep our credentials secure.

Create a file named .env in your project root:

# .env
THISKARD_SECRET_KEY=sk_test_your_secret_key_here
THISKARD_BASE_URL=https://api.thiskard.com/v1

Next, initialize your project and install the necessary dependencies. We will use axios for HTTP requests and dotenv to manage environment variables.

npm init -y
npm install axios dotenv express

Now, create an index.js file. This will serve as our testing script.

// index.js
require('dotenv').config();
const axios = require('axios');

// Configure Axios instance for Thiskard API
const thiskardClient = axios.create({
  baseURL: process.env.THISKARD_BASE_URL,
  headers: {
    'Authorization': `Bearer ${process.env.THISKARD_SECRET_KEY}`,
    'Content-Type': 'application/json'
  }
});

console.log('Thiskard Sandbox Client Initialized.');

Step 2: Simulating Cardholder Onboarding and Issuance

Before you can issue a card, you need a cardholder. In a production environment, this step involves passing Know Your Customer (KYC) verification. In the Thiskard Sandbox, we simulate this process to allow immediate testing.

The sandbox allows you to create a "Test Cardholder" where the KYC status is automatically set to VERIFIED. This lets you proceed to card issuance immediately without uploading physical documents.

Let's write a function to create a cardholder and then issue a virtual card.

async function createCardholderAndIssueCard() {
  try {
    // 1. Create a Cardholder
    console.log('Creating test cardholder...');
    const cardholderData = {
      first_name: 'Jane',
      last_name: 'Doe',
      email: '[email protected]',
      phone_number: '+15555550100',
      // Sandbox specific: Use specific metadata to simulate KYC approval
      metadata: {
        test_mode: 'auto_kyc_approve'
      }
    };

    const cardholderResponse = await thiskardClient.post('/cardholders', cardholderData);
    const cardholderId = cardholderResponse.data.id;
    console.log(`Cardholder created with ID: ${cardholderId}`);

    // 2. Issue a Virtual Card
    console.log('Issuing virtual card...');
    const cardData = {
      cardholder_id: cardholderId,
      type: 'VIRTUAL',
      currency: 'USD',
      // In Sandbox, we can specify initial limits or settings
      spend_controls: {
        limit_amount: 5000,
        limit_interval: 'MONTHLY'
      }
    };

    const cardResponse = await thiskardClient.post('/cards', cardData);
    const cardId = cardResponse.data.id;
    const last4 = cardResponse.data.last4;
    
    console.log(`Virtual Card Issued! ID: ${cardId}, Last 4: ${last4}`);
    
    return { cardholderId, cardId };

  } catch (error) {
    console.error('Error during issuance:', error.response ? error.response.data : error.message);
    throw error;
  }
}

// Execute the function
createCardholderAndIssueCard();

What's happening here? We are hitting the /cardholders endpoint to register the user. Once we have the unique cardholder_id, we use it to request a new card via the /cards endpoint.

In the sandbox, the card state transitions instantly to ACTIVE. In production, there might be a slight delay while the card network processes the request, which brings us to our next critical topic: webhooks.

Step 3: Setting Up Webhooks for Asynchronous Events

In a distributed system, APIs often operate asynchronously. While the API call to create a card returns a 201 Created status, the actual provisioning of the card with the network happens in the background. Your application needs to know when that process completes.

This is where webhooks come in. Webhooks are HTTP callbacks sent by Thiskard to your server when specific events occur.

The Local Webhook Challenge

Developers often struggle to test webhooks locally because localhost is not publicly accessible. To solve this, we will set up a simple Express server to receive webhooks and use a tool like ngrok to expose it.

Here is a simple server setup to listen for events:

// webhook_server.js
const express = require('express');
const app = express();
const port = 3000;

// Middleware to parse raw body (important for signature verification)
app.use(express.raw({ type: 'application/json' }));

app.post('/webhooks', (req, res) => {
  const signature = req.headers['thiskard-signature'];
  const payload = req.body;

  // TODO: Verify signature here (covered in Best Practices)

  const event = JSON.parse(payload);
  
  console.log(`Received event: ${event.type}`);
  console.log('Event Data:', JSON.stringify(event.data, null, 2));

  // Handle specific event types
  switch (event.type) {
    case 'card.issued':
      console.log(`Card ${event.data.card_id} has been successfully issued.`);
      // Update your database status to 'Active'
      break;
    case 'wallet.credited':
      console.log(`Wallet ${event.data.wallet_id} received ${event.data.amount}.`);
      break;
    default:
      console.log(`Unhandled event type: ${event.type}`);
  }

  // Return a 200 status to acknowledge receipt
  res.status(200).send('Received');
});

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

Run this server in a separate terminal window (node webhook_server.js). Then, use ngrok to tunnel it: ngrok http 3000.

Take the HTTPS URL provided by ngrok (e.g., https://abc1-2-3-4.ngrok.io) and configure it in your Thiskard Dashboard under Developers > Webhooks. Set the endpoint URL to https://<your-ngrok-url>/webhooks.

Now, when you run the createCardholderAndIssueCard script from Step 2, your local server should receive a card.issued event shortly after the API call returns.

Step 4: Simulating Wallet Top-Ups

A card is useless without funds. In a live environment, top-ups occur via bank transfers (ACH/Wire) or incoming debit transactions. In the Sandbox, waiting for a bank transfer simulation is unnecessary.

Thiskard provides a specialized endpoint for simulating balance updates. This is strictly a sandbox feature; calling this endpoint in production will return a 404 Not Found or 403 Forbidden.

Let's expand our script to top up the wallet associated with the cardholder we just created.