Overview

In this tutorial, you’ll learn how to implement, monitor, and manage scheduled savings deposits with the Blnk ledger. Here’s what we’ll do:

  1. Create a customer savings wallet balance.
  2. Initiate a scheduled deposit transaction. For one-time funding: Wallet Funding Tutorial →.
  3. Update the savings frequency.

For this tutorial, we’ll use the Blnk TypeScript SDK for the implementation. If you prefer, you can also refer to the API reference for details on the available endpoints.


Designing your map

Before writing code, it’s crucial to design a money movement map that outlines how money moves in your system. This serves as the blueprint for your implementation.

For our scheduled savings deposit workflow, here’s our map:

Read more: Scheduling transactions →


Set up your implementation

Based on our map, we’ll implement the following steps:

  1. Initiate a scheduled transaction to Savings Wallet
  2. Monitor the date of the next scheduled deposit to ensure timely execution.
  3. Set up a new scheduled transaction after the current one has been successfully processed.

Prerequisites

Before starting, ensure you have:

  1. A running Blnk server instance (e.g. at http://localhost:5001).
  2. An API key for Blnk (replace YOUR_API_KEY in the code examples). Required for authenticated requests.
  3. The Blnk CLI installed or a connected Blnk Cloud workspace to view your ledger data.
  4. A customer savings ledger to organise the customers’ savings balances. Learn how: How to Create a Ledger →

Create customer savings balance

Create a balance with the ledger_id of your Customer Savings Ledger to represent your customer savings wallet:

async function createSavingsWallet(savingsLedgerId, identityId, currency) {
  const blnk = await getBlnkInstance();
  const { LedgerBalances } = blnk;
  
  const savingsWallet = await LedgerBalances.create({
    ledger_id: savingsLedgerId,
    identity_id: identityId,
    currency: currency,
    meta_data: {
      wallet_type: "savings",
      schedule_savings: true,
      savings_frequency: "monthly",
      savings_amount: 10000,
      next_scheduled_savings: ""
    }
  });
  
  console.log("Savings Wallet created:", savingsWallet.data.balance_id);
  return savingsWallet.data.balance_id;
}

This creates a savings balance with a set of default metadata:

  1. This customer balance is subscribed to scheduled deposits.
  2. Schedule frequency is monthly.
  3. The next scheduled savings date can be monitored on the customer balance

Initiate scheduled transaction

Schedule a transaction on the ledger with the scheduled_for parameter:

async function createScheduledSavingsTransaction(savingsBalanceId, savingsAmount, uniqueReference, description, scheduledDate) {
  const blnk = await getBlnkInstance();
  const { Transactions } = blnk;
  
  const scheduledSavings = await Transactions.create({
    amount: savingsAmount,
    precision: 100,
    reference: uniqueReference,
    description: description || "Scheduled savings deposit",
    currency: "USD",
    source: "@WorldUSD",      // Represents external funding source
    destination: savingsBalanceId, 
    scheduled_for: scheduledDate,
    meta_data: {
      transaction_type: "savings"
    }
  });
  
  console.log("Savings successfully scheduled:", scheduledSavings.data.transaction_id);
  return scheduledSavings.data.transaction_id;
}

A SCHEDULED transaction is created waiting to be applied on the specified scheduled date.


Set up new scheduled transaction

To schedule the next savings deposit, verify that scheduled_savings is still active on the customer’s balance and retrieve its frequency.

Blnk sends a webhook when a scheduled transaction gets applied.

import express from 'express';
const app = express();
app.use(express.json());
app.post('/webhooks/transactions', async (req, res) => {
  const { event, data } = req.body;
  
  if (event === 'transaction.applied') {
    const transaction = data;
    
    if (
      transaction.meta_data?.transaction_type === 'savings'
    ) {
      console.log('Scheduled savings completed:', transaction.transaction_id);
      
      const blnk = await getBlnkInstance();
      const { LedgerBalances, Transactions } = blnk;
      
      const savingsBalanceId = transaction.destination;
      const savingsBalance = await LedgerBalances.retrieve(savingsBalanceId);
      
      if (
        savingsBalance.meta_data?.schedule_savings === true
      ) {
        const frequency = savingsBalance.meta_data.savings_frequency || "monthly";
        const amount = savingsBalance.meta_data.savings_amount;
        
        const nextScheduledDate = calculateNextScheduledDate(frequency);
        
        // 1. Update metadata with the next scheduled date
        await LedgerBalances.updateMetadata(savingsBalanceId, {
          "meta_data": {
            "next_scheduled_savings": nextScheduledDate.toISOString()
          }
        });
        
        // 2. Create a new scheduled transaction
        const uniqueReference = `sch-savings-${Date.now()}`;
        const scheduledSavings = await Transactions.create({
          amount: amount,
          precision: 100,
          reference: uniqueReference,
          description: "Scheduled savings deposit",
          currency: "USD",
          source: "@WorldUSD",
          destination: savingsBalanceId,
          scheduled_for: nextScheduledDate.toISOString(),
          meta_data: {
            transaction_type: "savings"
          }
        });
        
        console.log(`Next scheduled deposit created for ${nextScheduledDate.toISOString()}:`, scheduledSavings.data.transaction_id);
      } else {
        console.log(`Scheduled savings is no longer active for balance ${savingsBalanceId}`);
      }
    }
  }
  
  res.status(200).send('Webhook received');
});

function calculateNextScheduledDate(frequency) {
  const now = new Date();
  const nextDate = new Date(now);
  
  switch (frequency) {
    case 'daily':
      nextDate.setDate(now.getDate() + 1);
      break;
    case 'weekly':
      nextDate.setDate(now.getDate() + 7);
      break;
    case 'biweekly':
      nextDate.setDate(now.getDate() + 14);
      break;
    case 'monthly':
      nextDate.setMonth(now.getMonth() + 1);
      break;
    default:
      nextDate.setMonth(now.getMonth() + 1);
  }
  
  return nextDate;
}

const PORT = process.env.PORT || 3000;
app.listen(PORT, () => {
  console.log(`Webhook server listening on port ${PORT}`);
});

Change savings frequency

To change the savings frequency of a balance, use the Update Metadata endpoint:

async function updateSavingsFrequency(balanceId, newFrequency) {
  const blnk = await getBlnkInstance();
  const { LedgerBalances } = blnk;
  
  try {
    const response = await LedgerBalances.updateMetadata(balanceId, {
      "meta_data": {
        "savings_frequency": newFrequency
      }
    });
    
    console.log(`Successfully updated savings frequency to ${newFrequency} for balance ${balanceId}`);
    return response.data;
  } catch (error) {
    console.error(`Failed to update savings frequency for balance ${balanceId}:`, error);
    throw error;
  }
}

Conclusion

In this tutorial, we’ve built a scheduled savings workflow for a wallet app using Blnk. Here’s what we covered:

  • Setting up savings ledgers and customer savings accounts
  • Adding scheduled deposits
  • Automatically rescheduling deposits after they’re completed
  • Handling deposit frequencies and customer preferences
  • Using webhooks to detect completed transactions and schedule new deposits.

With these features, your app offers customers an easy, automated way to save regularly without extra effort. The webhook-based architecture ensures that your system remains resilient and responsive to transaction events.


Can’t find your use case?

If you’re searching for a specific tutorial that isn’t included here, you’re welcome to contribute to our documentation by sharing your expertise or requesting a new tutorial in our Discord community.

We’re always thrilled to expand our resources with the help of our developer community!