> ## Documentation Index
> Fetch the complete documentation index at: https://docs.blnkfinance.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Handling Deposits & Payouts

> Learn how to manage and track deposits and payouts with Blnk.

export const RelatedTopics = ({title = "Related topics", items = []}) => {
  if (!items.length) {
    return null;
  }
  return <nav className="related-topics not-prose mt-20 mb-10 flex flex-col" aria-label={title}>
      <p className="related-topics-heading m-0 border-b border-zinc-200 pb-3 text-sm font-medium text-zinc-500 dark:border-white/10 dark:text-zinc-400">
        {title}
      </p>
      <ul className="related-topics-list m-0 mt-3 flex list-none flex-col gap-0.5 p-0">
        {items.map(item => {
    const isExternal = typeof item.href === "string" && (/^https?:\/\//i).test(item.href);
    return <li key={item.href} className="m-0 p-0">
              <a href={item.href} target={isExternal ? "_blank" : undefined} rel={isExternal ? "noopener noreferrer" : undefined} className="related-topics-link group inline-flex items-center gap-2 text-sm font-semibold text-zinc-700 no-underline transition-colors dark:text-zinc-300">
                <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="16" height="16" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" className="related-topics-icon shrink-0 text-zinc-400 dark:text-zinc-500" aria-hidden="true">
                  <path d="M15 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V7Z" />
                  <path d="M14 2v4a2 2 0 0 0 2 2h4" />
                  <path d="M10 9H8" />
                  <path d="M16 13H8" />
                  <path d="M16 17H8" />
                </svg>
                <span className="relative top-px transition-colors group-hover:text-[#DD7B1B]">
                  {item.title}
                </span>
              </a>
            </li>;
  })}
      </ul>
    </nav>;
};

export const CtaCallout = props => {
  const {title, buttonLabel, href, trackingEvent, buttonTarget, rel = "noopener noreferrer", children} = props;
  const handleCtaClick = () => {
    if (typeof window === "undefined" || !trackingEvent) {
      return;
    }
    try {
      window.dispatchEvent(new CustomEvent("blnk:docs-cta", {
        detail: {
          name: trackingEvent,
          href
        }
      }));
    } catch {}
    try {
      window.posthog?.capture?.(trackingEvent, {
        href
      });
    } catch {}
    const gaPayload = {
      cta_href: href
    };
    try {
      window.gtag?.("event", trackingEvent, gaPayload);
    } catch {}
    try {
      window.dataLayer = window.dataLayer || [];
      window.dataLayer.push({
        event: trackingEvent,
        ...gaPayload
      });
    } catch {}
  };
  const isExternal = typeof href === "string" && (/^https?:\/\//i).test(href);
  const target = buttonTarget ?? (isExternal ? "_blank" : undefined);
  const linkRel = isExternal ? rel : undefined;
  return <section className="cta-callout not-prose relative my-8 w-full min-w-0 overflow-hidden rounded-xl border border-zinc-200 p-5 dark:border-white/10">
      <div className="cta-callout-noise" aria-hidden="true" />
      <div className="cta-callout-layout">
        {title ? <div className="cta-callout-title-row">
            <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 28 28" width="14" height="14" className="cta-callout-icon shrink-0 text-zinc-800 dark:text-zinc-200" aria-hidden="true">
              <g fill="none" fillRule="nonzero">
                <path d="M28 0v28H0V0h28ZM14.691833333333335 27.134333333333334l-0.012833333333333334 0.0023333333333333335 -0.08283333333333333 0.04083333333333334 -0.023333333333333334 0.004666666666666667 -0.016333333333333335 -0.004666666666666667 -0.08283333333333333 -0.04083333333333334c-0.011666666666666667 -0.004666666666666667 -0.022166666666666668 -0.0011666666666666668 -0.028000000000000004 0.005833333333333334l-0.004666666666666667 0.011666666666666667 -0.019833333333333335 0.49933333333333335 0.005833333333333334 0.023333333333333334 0.011666666666666667 0.015166666666666667 0.12133333333333333 0.08633333333333333 0.0175 0.004666666666666667 0.014000000000000002 -0.004666666666666667 0.12133333333333333 -0.08633333333333333 0.014000000000000002 -0.018666666666666668 0.004666666666666667 -0.019833333333333335 -0.019833333333333335 -0.4981666666666667c-0.0023333333333333335 -0.011666666666666667 -0.0105 -0.019833333333333335 -0.019833333333333335 -0.021Zm0.3091666666666667 -0.13183333333333336 -0.015166666666666667 0.0023333333333333335 -0.21583333333333335 0.1085 -0.011666666666666667 0.011666666666666667 -0.0035000000000000005 0.012833333333333334 0.021 0.5016666666666667 0.005833333333333334 0.014000000000000002 0.009333333333333334 0.008166666666666668 0.23450000000000004 0.1085c0.014000000000000002 0.004666666666666667 0.026833333333333334 0 0.03383333333333334 -0.009333333333333334l0.004666666666666667 -0.016333333333333335 -0.03966666666666667 -0.7163333333333334c-0.0035000000000000005 -0.014000000000000002 -0.011666666666666667 -0.023333333333333334 -0.023333333333333334 -0.025666666666666667Zm-0.8341666666666667 0.0023333333333333335a0.026833333333333334 0.026833333333334334 0 0 0 -0.0315 0.007000000000000001l-0.007000000000000001 0.016333333333333335 -0.03966666666666667 0.7163333333333334c0 0.014000000000000002 0.008166666666666668 0.023333333333333334 0.019833333333333335 0.028000000000000004l0.0175 -0.0023333333333333335 0.23450000000000004 -0.1085 0.011666666666666667 -0.009333333333333334 0.004666666666666667 -0.012833333333333334 0.019833333333333335 -0.5016666666666667 -0.0035000000000000005 -0.014000000000000002 -0.011666666666666667 -0.011666666666666667 -0.21466666666666667 -0.10733333333333334Z" strokeWidth="1.1667" />
                <path fill="currentColor" d="M14 2.916666666666667A1.75 1.75 0 0 1 15.750000000000002 4.666666666666667v6.302333333333334L21.207666666666668 7.816666666666667a1.75 1.75 0 0 1 1.75 3.031L17.5 14l5.457666666666667 3.151166666666667a1.75 1.75 0 0 1 -1.75 3.031l-5.457666666666667 -3.1500000000000004V23.333333333333336a1.75 1.75 0 0 1 -3.5 0v-6.302333333333334L6.792333333333334 20.183333333333337a1.75 1.75 0 1 1 -1.75 -3.031L10.5 14 5.042333333333334 10.848833333333333a1.75 1.75 0 0 1 1.75 -3.031l5.457666666666667 3.1500000000000004V4.666666666666667A1.75 1.75 0 0 1 14 2.916666666666667Z" strokeWidth="1.1667" />
              </g>
            </svg>
            <p className="cta-callout-title min-w-0 font-semibold text-zinc-800 dark:text-zinc-200">
              {title}
            </p>
          </div> : null}
        <div className={`cta-callout-body text-sm leading-normal text-zinc-800 dark:text-zinc-200${title ? " cta-callout-body--indented" : ""}`}>
          {children}
        </div>
        <a href={href} target={target} rel={linkRel} onClick={handleCtaClick} data-docs-cta={trackingEvent || undefined} className="cta-callout-button inline-flex items-center justify-center gap-1 rounded-full bg-white px-3 py-1.5 text-sm font-semibold transition hover:bg-zinc-100 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-white/50 dark:bg-white dark:hover:bg-zinc-200">
          {buttonLabel}
          <span className="cta-callout-button-arrow" aria-hidden="true">
            →
          </span>
        </a>
      </div>
    </section>;
};

In this tutorial, you’ll learn how to implement deposits and payouts using the Blnk Ledger. You’ll explore and apply a variety of workflows to handle deposits and payouts seamlessly. This includes:

1. Understanding how to set up your ledger for effective tracking of deposits and payouts in your system.
2. Designing flexible workflows to accommodate various scenarios.
3. Ensuring reliable and accurate transaction processing for your users.

For this tutorial, we’ll use the [Blnk TypeScript SDK](/sdks/typescript) for the implementation. If you prefer, you can also refer to the [API reference](/reference/create-ledger) for details on the available endpoints.

***

## Prerequisites

Before starting, ensure you have:

1. A running Blnk Core 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. Optionally, you can connect your Blnk Core to your [Blnk Cloud](https://cloud.blnkfinance.com/?utm_source=blnk_docs\&utm_medium=documentation\&utm_campaign=tutorials%2Fdigital-banking%2Fdeposits-withdrawals) workspace to view your ledger data.

To prepare for the rest of this tutorial, read our [Building a Wallet with Blnk](/tutorials/quick-start/wallet-management) tutorial if you haven’t already.

This resource will walk you through creating ledger balances for accounts in Blnk, which are essential for implementing deposits, withdrawals, and other financial operations covered later in this tutorial.

***

## Handling deposits

When using Blnk to manage deposits, you can track the sources of your deposits-such as Stripe, Acme Bank, or others-directly in your ledger, enabling more detailed and accurate reporting.

As illustrated in the map below, each source can be assigned to an internal balance, which will be reflected as the value of the `source` field in your transaction request. Fees (1% of the deposit amount, capped at \$5) are deducted and tracked separately.

<img src="https://mintcdn.com/blnk/jKiGmb7nTD9y-R5a/images/tutorials/deposits-withdrawals/handling-deposits-map.png?fit=max&auto=format&n=jKiGmb7nTD9y-R5a&q=85&s=7e498aec18621712b57e7c8a0cda6f0e" alt="Handling deposits map" width="823" height="479" data-path="images/tutorials/deposits-withdrawals/handling-deposits-map.png" />

[Explore the map yourself here](https://map.blnkfinance.xyz/lsLqJyW8VQ)

From our map, we can verify:

* The source of the deposit, for example, @AcmeBank if it originates from Acme Bank.
* That the customer receives funds after the fees have been dededucted.
* The deposit fees are tracked using the `@Fees` balance.

### Deposits with fee processing

When recording a deposit with fee processing, we’ll use the [Multiple Destinations](/transactions/multiple-destinations) feature to distribute the incoming funds between the customer’s balance and the fees balance.

We’ll also apply `inflight` to validate the deposits against predefined business rules before finalizing the transaction.

<CodeGroup>
  ```bash cURL wrap expandable theme={"system"}
  curl -X POST "http://localhost:5001/transactions" \
    -H "X-blnk-key: <api-key>" \
    -H "Content-Type: application/json" \
    -d '{
      "precise_amount": 10000,
      "precision": 100,
      "reference": "stripe_payment_ref_001",
      "currency": "USD",
      "source": "@Stripe",
      "inflight": true,
      "skip_queue": true,
      "destinations": [
        {
          "identifier": "bln_CUSTOMER_BALANCE_ID",
          "distribution": "99.5",
          "narration": "Deposit to your account"
        },
        {
          "identifier": "@Fees",
          "distribution": "0.5",
          "narration": "Processing fee"
        }
      ],
      "meta_data": {
        "fee_amount": 0.5,
        "stripe_payment_id": "pi_123456"
      }
    }'
  ```

  ```typescript TypeScript wrap expandable theme={"system"}
  async function recordDeposit(
    sourceId,
    customerBalanceId,
    amount,
    reference,
    stripePaymentId,
  ) {
    
    // Calculate fee (1% capped at $5)
    const feePercentage = 0.01;
    const feeCap = 5;
    const calculatedFee = Math.min(amount * feePercentage, feeCap);
    const customerAmount = amount - calculatedFee;
    
    // Record the deposit with multiple destinations
    const transaction = await blnk.Transactions.create({
      precise_amount: amount * 100,                  // Total amount being deposited
      precision: 100,                 
      reference: reference,          
      currency: "USD",
      source: sourceId,               
      inflight: true,                  // Hold for verification
      destinations: [
        {
          identifier: customerBalanceId,
          distribution: `${customerAmount}`,                
          narration: "Deposit to your account"
        },
        {
          identifier: "@Fees",           
          distribution: `${calculatedFee}`, 
          narration: "Processing fee"
        }
      ],
      meta_data: {
        fee_amount: calculatedFee,
        stripe_payment_id: stripePaymentId
      },
      skip_queue: true                // Process immediately, bypassing queue
    });
    
    console.log(`Deposit recorded with ID: ${transaction.data.transaction_id}`);
    return transaction.data;
  }
  ```

  ```go Go wrap expandable theme={"system"}
  func recordDeposit(
      sourceID, customerBalanceID string,
      amount float64,
      reference, stripePaymentID string,
  ) (*blnkgo.Transaction, error) {
      client := getClient()

      // Calculate fee (1% capped at $5)
      feePercentage := 0.01
      feeCap := 5.0
      calculatedFee := math.Min(amount*feePercentage, feeCap)
      customerAmount := amount - calculatedFee

      // Record the deposit with multiple destinations
      transaction, _, err := client.Transaction.Create(
          blnkgo.CreateTransactionRequest{
              ParentTransaction: blnkgo.ParentTransaction{
                  PreciseAmount: big.NewInt(int64(amount * 100)),
                  Precision: 100,
                  Reference: reference,
                  Currency: "USD",
                  Source: sourceID,
                  SkipQueue: true,
                  Destinations: []blnkgo.Source{
                      {
                          Identifier: customerBalanceID,
                          Distribution: fmt.Sprintf("%g", customerAmount),
                      },
                      {
                          Identifier: "@Fees",
                          Distribution: fmt.Sprintf("%g", calculatedFee),
                      },
                  },
                  MetaData: blnkgo.MetaData{
                      "fee_amount": calculatedFee,
                      "stripe_payment_id": stripePaymentID,
                  },
              },
              Inflight: true,
          },
      )
      if err != nil {
          return nil, err
      }

      fmt.Printf("Deposit recorded with ID: %s\n", transaction.TransactionID)
      return transaction, nil
  }
  ```

  ```python Python wrap expandable theme={"system"}
  def record_deposit(source_id, customer_balance_id, amount, reference, stripe_payment_id):
      # Calculate fee (1% capped at $5)
      fee_percentage = 0.01
      fee_cap = 5
      calculated_fee = min(amount * fee_percentage, fee_cap)
      customer_amount = amount - calculated_fee

      # Record the deposit with multiple destinations
      transaction = blnk.transactions.create({
          "precise_amount": amount * 100,
          "precision": 100,
          "reference": reference,
          "currency": "USD",
          "source": source_id,
          "inflight": True,
          "destinations": [
              {
                  "identifier": customer_balance_id,
                  "distribution": f"{customer_amount}",
                  "narration": "Deposit to your account",
              },
              {
                  "identifier": "@Fees",
                  "distribution": f"{calculated_fee}",
                  "narration": "Processing fee",
              },
          ],
          "meta_data": {
              "fee_amount": calculated_fee,
              "stripe_payment_id": stripe_payment_id,
          },
          "skip_queue": True,
      })

      print(f"Deposit recorded with ID: {transaction.data['transaction_id']}")
      return transaction.data
  ```

  ```java Java wrap expandable theme={"system"}
  double feePercentage = 0.01;
  double feeCap = 5;
  double calculatedFee = Math.min(amount * feePercentage, feeCap);
  double customerAmount = amount - calculatedFee;

  ApiResponse<JsonNode> transaction = blnk.transactions().create(
      CreateTransactions.create()
          .preciseAmount(amount * 100)
          .precision(100)
          .reference(reference)
          .currency("USD")
          .source(sourceId)
          .inflight(true)
          .destinations(List.of(
              MultipleSourcesT.create()
                  .identifier(customerBalanceId)
                  .distribution(String.valueOf(customerAmount))
                  .narration("Deposit to your account"),
              MultipleSourcesT.create()
                  .identifier("@Fees")
                  .distribution(String.valueOf(calculatedFee))
                  .narration("Processing fee")
          ))
          .metaData(Map.of(
              "fee_amount", calculatedFee,
              "stripe_payment_id", stripePaymentId
          ))
          .skipQueue(true)
  );

  System.out.println(
      "Deposit recorded with ID: " + transaction.data().get("transaction_id").asText()
  );
  ```
</CodeGroup>

<Tip>
  When you apply `inflight` to a multiple destinations transaction, all associated transactions-distributing funds to both destinations-are also placed in an `inflight` state until the commit or void is executed.
</Tip>

### Verifying deposits based on business rules

Next, we’ll develop functions to process deposits according to the following business rules. In this tutorial, we’ll implement checks for two rules:

1. If the balance is blocked or frozen, the deposit should be voided.

2. If the deposit amount exceeds 1 million USD, it should remain in an `inflight` state, and a notification should be sent for further investigation.

#### Rule 1: Check if balance is blocked or frozen

<CodeGroup>
  ```bash cURL wrap expandable theme={"system"}
  # Get transaction details
  curl -X GET "http://localhost:5001/transactions/{transaction_id}" \
    -H "X-blnk-key: <api-key>"

  # Get customer balance metadata
  curl -X GET "http://localhost:5001/balances/{balance_id}" \
    -H "X-blnk-key: <api-key>"

  # Add void reason metadata
  curl -X POST "http://localhost:5001/{transaction_id}/metadata" \
    -H "X-blnk-key: <api-key>" \
    -H "Content-Type: application/json" \
    -d '{
      "meta_data": {
        "void_reason": "Account frozen"
      }
    }'

  # Void inflight transaction if account is frozen/blocked
  curl -X PUT "http://localhost:5001/transactions/inflight/{transaction_id}" \
    -H "X-blnk-key: <api-key>" \
    -H "Content-Type: application/json" \
    -d '{
      "status": "void"
    }'
  ```

  ```typescript TypeScript wrap expandable theme={"system"}
  async function processDepositBasedOnRules(transactionId) {
    const baseUrl = process.env.BLNK_BASE_URL ?? 'http://localhost:5001';
    const apiKey = process.env.BLNK_API_KEY ?? '';

    const txnRes = await fetch(`${baseUrl}/transactions/${transactionId}`, {
      headers: { 'X-Blnk-Key': apiKey },
    });
    if (!txnRes.ok) throw new Error(await txnRes.text());
    const transaction = await txnRes.json();

    const destinations = transaction.destinations || [];
    const customerBalanceId = destinations[0]?.identifier;

    const balanceResponse = await blnk.LedgerBalances.get(customerBalanceId);
    const meta = balanceResponse.data?.meta_data || {};

    if (meta.status === 'frozen' || meta.status === 'blocked') {
      const metaUpdate = await fetch(`${baseUrl}/${transactionId}/metadata`, {
        method: 'POST',
        headers: {
          'X-Blnk-Key': apiKey,
          'Content-Type': 'application/json',
        },
        body: JSON.stringify({
          meta_data: { void_reason: `Account ${meta.status}` },
        }),
      });
      if (!metaUpdate.ok) throw new Error(await metaUpdate.text());

      const voidResult = await blnk.Transactions.updateStatus(transactionId, {
        status: 'void',
      });
      if (voidResult.status !== 200 || !voidResult.data) {
        throw new Error(voidResult.message ?? 'Failed to void transaction');
      }
      console.log(`Transaction ${transactionId} voided: account ${meta.status}`);
      return voidResult.data;
    }

    return await processDepositByAmount(transactionId);
  }
  ```

  ```go Go wrap expandable theme={"system"}
  func processDepositBasedOnRules(transactionID string) (*blnkgo.Transaction, error) {
      client := getClient()

      transaction, _, err := client.Transaction.Get(transactionID)
      if err != nil {
          return nil, err
      }

      var customerBalanceID string
      if len(transaction.Destinations) > 0 {
          customerBalanceID = transaction.Destinations[0].Identifier
      }

      balanceResponse, _, err := client.LedgerBalance.Get(
          customerBalanceID,
      )
      if err != nil {
          return nil, err
      }

      status, _ := balanceResponse.MetaData["status"].(string)
      if status == "frozen" || status == "blocked" {
          _, _, err = client.Metadata.UpdateMetadata(
              transactionID,
              blnkgo.UpdateMetaDataRequest{
                  MetaData: blnkgo.MetaData{
                      "void_reason": fmt.Sprintf("Account %s", status),
                  },
              },
          )
          if err != nil {
              return nil, err
          }

          voidResult, _, err := client.Transaction.Update(
              transactionID,
              blnkgo.UpdateStatus{
                  Status: "void",
              },
          )
          if err != nil {
              return nil, err
          }

          fmt.Printf("Transaction %s voided: account %s\n", transactionID, status)
          return voidResult, nil
      }

      return processDepositByAmount(transactionID)
  }
  ```

  ```python Python wrap expandable theme={"system"}
  def process_deposit_based_on_rules(transaction_id):
      transaction = blnk.transactions.get(transaction_id)
      transaction_data = transaction.data

      destinations = transaction_data.get("destinations") or []
      customer_balance_id = destinations[0]["identifier"] if destinations else None

      balance_response = blnk.ledger_balances.get(customer_balance_id)
      meta = balance_response.data.get("meta_data") or {}

      if meta.get("status") in ("frozen", "blocked"):
          blnk.metadata.update(transaction_id, {
              "meta_data": {
                  "void_reason": f"Account {meta['status']}",
              },
          })

          void_result = blnk.transactions.update_status(transaction_id, {
              "status": "void",
          })
          if void_result.status != 200 or not void_result.data:
              raise Exception(void_result.message or "Failed to void transaction")

          print(f"Transaction {transaction_id} voided: account {meta['status']}")
          return void_result.data

      return process_deposit_by_amount(transaction_id)
  ```

  ```java Java wrap expandable theme={"system"}
  ApiResponse<JsonNode> balanceResponse = blnk.ledgerBalances().get(customerBalanceId);
  ```
</CodeGroup>

#### Rule 2: Check amount rule

<CodeGroup>
  ```bash cURL wrap expandable theme={"system"}
  # Get transaction amount
  curl -X GET "http://localhost:5001/transactions/{transaction_id}" \
    -H "X-blnk-key: <api-key>"

  # Commit if below threshold ($1M)
  curl -X PUT "http://localhost:5001/transactions/inflight/{transaction_id}" \
    -H "X-blnk-key: <api-key>" \
    -H "Content-Type: application/json" \
    -d '{
      "status": "commit"
    }'

  # Or flag large deposits for investigation
  curl -X POST "http://localhost:5001/{transaction_id}/metadata" \
    -H "X-blnk-key: <api-key>" \
    -H "Content-Type: application/json" \
    -d '{
      "meta_data": {
        "investigation_required": true,
        "investigation_reason": "Large deposit amount",
        "flagged_at": "2024-12-21T01:36:46.997063436Z"
      }
    }'
  ```

  ```typescript TypeScript wrap expandable theme={"system"}
  async function processDepositByAmount(transactionId) {
    const baseUrl = process.env.BLNK_BASE_URL ?? 'http://localhost:5001';
    const apiKey = process.env.BLNK_API_KEY ?? '';

    const txnRes = await fetch(`${baseUrl}/transactions/${transactionId}`, {
      headers: { 'X-Blnk-Key': apiKey },
    });
    if (!txnRes.ok) throw new Error(await txnRes.text());
    const transaction = await txnRes.json();
    const amount = transaction.precise_amount;

    if (amount < 100000000) {
      const commit = await blnk.Transactions.updateStatus(transactionId, {
        status: 'commit',
      });
      if (commit.status !== 200 || !commit.data) {
        throw new Error(commit.message ?? 'Failed to commit transaction');
      }
      console.log(`Transaction ${transactionId} committed: amount below threshold`);
      return commit.data;
    }

    const metaRes = await fetch(`${baseUrl}/${transactionId}/metadata`, {
      method: 'POST',
      headers: {
        'X-Blnk-Key': apiKey,
        'Content-Type': 'application/json',
      },
      body: JSON.stringify({
        meta_data: {
          investigation_required: true,
          investigation_reason: 'Large deposit amount',
          flagged_at: new Date().toISOString(),
        },
      }),
    });
    if (!metaRes.ok) throw new Error(await metaRes.text());
    const flagged = await metaRes.json();

    await triggerLargeDepositHook(transactionId, amount);
    console.log(`Transaction ${transactionId} flagged for investigation: large amount`);
    return flagged;
  }
  ```

  ```go Go wrap expandable theme={"system"}
  func processDepositByAmount(transactionID string) (interface{}, error) {
      client := getClient()

      transaction, _, err := client.Transaction.Get(transactionID)
      if err != nil {
          return nil, err
      }

      amount, _ := transaction.PreciseAmount.Int64()

      if amount < 100000000 {
          commit, _, err := client.Transaction.Update(
              transactionID,
              blnkgo.UpdateStatus{
                  Status: "commit",
              },
          )
          if err != nil {
              return nil, err
          }

          fmt.Printf("Transaction %s committed: amount below threshold\n", transactionID)
          return commit, nil
      }

      flagged, _, err := client.Metadata.UpdateMetadata(
          transactionID,
          blnkgo.UpdateMetaDataRequest{
              MetaData: blnkgo.MetaData{
                  "investigation_required": true,
                  "investigation_reason": "Large deposit amount",
                  "flagged_at": time.Now().UTC().Format(time.RFC3339),
              },
          },
      )
      if err != nil {
          return nil, err
      }

      triggerLargeDepositHook(transactionID, amount)
      fmt.Printf("Transaction %s flagged for investigation: large amount\n", transactionID)
      return flagged, nil
  }
  ```

  ```python Python wrap expandable theme={"system"}
  from datetime import datetime

  def process_deposit_by_amount(transaction_id):
      transaction = blnk.transactions.get(transaction_id)
      amount = transaction.data["precise_amount"]

      if amount < 100000000:
          commit = blnk.transactions.update_status(transaction_id, {
              "status": "commit",
          })
          if commit.status != 200 or not commit.data:
              raise Exception(commit.message or "Failed to commit transaction")

          print(f"Transaction {transaction_id} committed: amount below threshold")
          return commit.data

      flagged = blnk.metadata.update(transaction_id, {
          "meta_data": {
              "investigation_required": True,
              "investigation_reason": "Large deposit amount",
              "flagged_at": datetime.utcnow().isoformat(),
          },
      })

      trigger_large_deposit_hook(transaction_id, amount)
      print(f"Transaction {transaction_id} flagged for investigation: large amount")
      return flagged
  ```

  ```java Java wrap expandable theme={"system"}
  ApiResponse<JsonNode> commit = blnk.transactions().updateStatus(
      transactionId,
      UpdateTransactionStatus.create()
          .status("commit")
  );
  ```
</CodeGroup>

Read more: [Webhooks overview →](/webhooks/overview)

### Putting it all together

<CodeGroup>
  ```bash cURL wrap expandable theme={"system"}
  # Step 1: Record deposit with fee processing (see recordDeposit)
  curl -X POST "http://localhost:5001/transactions" \
    -H "X-blnk-key: <api-key>" \
    -H "Content-Type: application/json" \
    -d '{
      "precise_amount": 10000,
      "precision": 100,
      "reference": "stripe_pi_123456",
      "currency": "USD",
      "source": "@Stripe",
      "inflight": true,
      "skip_queue": true,
      "destinations": [
        {"identifier": "bln_CUSTOMER_BALANCE_ID", "distribution": "99.5"},
        {"identifier": "@Fees", "distribution": "0.5"}
      ]
    }'

  # Step 2: Apply business rules (see processDepositBasedOnRules)
  curl -X GET "http://localhost:5001/transactions/{transaction_id}" \
    -H "X-blnk-key: <api-key>"
  ```

  ```typescript TypeScript wrap expandable theme={"system"}
  async function handleStripeDeposit(stripePaymentId, customerBalanceId, amount) {
    
    // Step 1: Record the deposit with fee processing
    const reference = `stripe_${stripePaymentId}`;
    const depositData = await recordDeposit(
      "@Stripe", 
      customerBalanceId, 
      amount, 
      reference,
      stripePaymentId
    );
    
    // Step 2: Process the deposit based on business rules
    const result = await processDepositBasedOnRules(depositData.transaction_id);
    
    return {
      message: `Deposit processed with status: ${result.status}`
    };
  }
  ```

  ```go Go wrap expandable theme={"system"}
  func handleStripeDeposit(
      stripePaymentID, customerBalanceID string,
      amount float64,
  ) (map[string]string, error) {
      // Step 1: Record the deposit with fee processing
      reference := fmt.Sprintf("stripe_%s", stripePaymentID)
      depositData, err := recordDeposit(
          "@Stripe",
          customerBalanceID,
          amount,
          reference,
          stripePaymentID,
      )
      if err != nil {
          return nil, err
      }

      // Step 2: Process the deposit based on business rules
      result, err := processDepositBasedOnRules(depositData.TransactionID)
      if err != nil {
          return nil, err
      }

      return map[string]string{
          "message": fmt.Sprintf("Deposit processed with status: %s", result.Status),
      }, nil
  }
  ```

  ```python Python wrap expandable theme={"system"}
  def handle_stripe_deposit(stripe_payment_id, customer_balance_id, amount):
      # Step 1: Record the deposit with fee processing
      reference = f"stripe_{stripe_payment_id}"
      deposit_data = record_deposit(
          "@Stripe",
          customer_balance_id,
          amount,
          reference,
          stripe_payment_id,
      )

      # Step 2: Process the deposit based on business rules
      result = process_deposit_based_on_rules(deposit_data["transaction_id"])

      return {
          "message": f"Deposit processed with status: {result['status']}",
      }
  ```

  ```java Java wrap expandable theme={"system"}
  String reference = "stripe_" + stripePaymentId;
  JsonNode depositData = recordDeposit(
      "@Stripe",
      customerBalanceId,
      amount,
      reference,
      stripePaymentId
  );

  JsonNode result = processDepositBasedOnRules(
      depositData.get("transaction_id").asText()
  );
  System.out.println(
      "Deposit processed with status: " + result.get("status").asText()
  );
  ```
</CodeGroup>

### Refunding deposits

To process refunds in your ledger and reverse a deposit:

<CodeGroup>
  ```bash cURL wrap expandable theme={"system"}
  curl -X POST "http://localhost:5001/refund-transaction/{transaction_id}" \
    -H "X-blnk-key: <api-key>"

  curl -X POST "http://localhost:5001/{refund_id}/metadata" \
    -H "X-blnk-key: <api-key>" \
    -H "Content-Type: application/json" \
    -d '{
      "meta_data": {
        "refund_reason": "Customer requested refund"
      }
    }'
  ```

  ```typescript TypeScript wrap expandable theme={"system"}
  async function refundDeposit(transactionId, reason = 'Customer requested refund') {
    
    // Process the refund
    const refund = await blnk.Transactions.refund(transactionId);
    
    const baseUrl = process.env.BLNK_BASE_URL ?? 'http://localhost:5001';
    const apiKey = process.env.BLNK_API_KEY ?? '';
    const metaRes = await fetch(`${baseUrl}/${refund.data.refund_id}/metadata`, {
      method: 'POST',
      headers: {
        'X-Blnk-Key': apiKey,
        'Content-Type': 'application/json',
      },
      body: JSON.stringify({
        meta_data: { refund_reason: reason },
      }),
    });
    if (!metaRes.ok) throw new Error(await metaRes.text());

    console.log(`Deposit refunded: ${refund.data.refund_id}`);
    return refund.data;
  }
  ```

  ```go Go wrap expandable theme={"system"}
  func refundDeposit(transactionID, reason string) (*blnkgo.Transaction, error) {
      client := getClient()
      if reason == "" {
          reason = "Customer requested refund"
      }

      refund, _, err := client.Transaction.Refund(transactionID)
      if err != nil {
          return nil, err
      }

      _, _, err = client.Metadata.UpdateMetadata(
          refund.TransactionID,
          blnkgo.UpdateMetaDataRequest{
              MetaData: blnkgo.MetaData{
                  "refund_reason": reason,
              },
          },
      )
      if err != nil {
          return nil, err
      }

      fmt.Printf("Deposit refunded: %s\n", refund.TransactionID)
      return refund, nil
  }
  ```

  ```python Python wrap expandable theme={"system"}
  def refund_deposit(transaction_id, reason="Customer requested refund"):
      # Process the refund
      refund = blnk.transactions.refund(transaction_id)

      blnk.metadata.update(refund.data["refund_id"], {
          "meta_data": {
              "refund_reason": reason,
          },
      })

      print(f"Deposit refunded: {refund.data['refund_id']}")
      return refund.data
  ```

  ```java Java wrap expandable theme={"system"}
  ApiResponse<JsonNode> refund = blnk.transactions().refund(transactionId);
  ```
</CodeGroup>

***

## Handling payouts

Payouts function similarly to deposits but in reverse.

The `Customer Balance` serves as the source, while the `Payout Destination` and `@Fees` act as the destinations. Include `@Fees` if the customer incurs fees for payment processing.

<img src="https://mintcdn.com/blnk/jKiGmb7nTD9y-R5a/images/tutorials/deposits-withdrawals/handling-payouts-ap.png?fit=max&auto=format&n=jKiGmb7nTD9y-R5a&q=85&s=01e6713314b1dd330cda9a788a0fe267" alt="Handling payouts map" width="901" height="468" data-path="images/tutorials/deposits-withdrawals/handling-payouts-ap.png" />

From our map, we can verify:

* The destination of the withdrawal.
* That the customer receives funds after the fees have been deducted.
* The deposit fees are tracked using the `@Fees` balance.

### Path 1: Successful payouts

Here, the payout was successfully completed by your payout provider:

<CodeGroup>
  ```bash cURL wrap expandable theme={"system"}
  curl -X POST "http://localhost:5001/transactions" \
    -H "X-blnk-key: <api-key>" \
    -H "Content-Type: application/json" \
    -d '{
      "precise_amount": 10100,
      "precision": 100,
      "reference": "payout_ref_001",
      "description": "Payout for bills",
      "currency": "USD",
      "source": "bln_CUSTOMER_BALANCE_ID",
      "inflight": true,
      "inflight_expiry_date": "2024-12-22T01:36:46.997063436Z",
      "skip_queue": true,
      "destinations": [
        {
          "identifier": "@BillsPayment",
          "distribution": "100",
          "narration": "Payout for bills"
        },
        {
          "identifier": "@Fees",
          "distribution": "1",
          "narration": "Payout processing fee"
        }
      ],
      "meta_data": {
        "payout_status": "pending",
        "fee_amount": 1
      }
    }'
  ```

  ```typescript TypeScript wrap expandable theme={"system"}
  // Initiate a payout with inflight
  async function initiatePayout(customerBalanceId, billsAmount, reference) {

    // Calculate fee (0.5% with $1 minimum)
    const feePercentage = 0.005;
    const minFee = 1;
    const calculatedFee = Math.max(billsAmount * feePercentage, minFee);
    const totalAmount = billsAmount + calculatedFee;
    
    const payout = await blnk.Transactions.create({
      precise_amount: totalAmount * 100,
      precision: 100,
      reference: reference,
      description: "Payout for bills",
      currency: "USD",
      source: customerBalanceId,
      destinations: [
        {
          identifier: "@BillsPayment",
          distribution: `${billsAmount}`,
          narration: "Payout for bills"
        },
        {
          identifier: "@Fees",
          distribution: `${calculatedFee}`,
          narration: "Payout processing fee"
        }
      ],
      inflight: true,
      inflight_expiry_date: getExpiryDate(24), // Expires if no response is gotten in 24 hours
      meta_data: {
        payout_status: "pending",
        fee_amount: calculatedFee
      },
      skip_queue: true                         // Process immediately, bypassing queue
    });
    
    console.log(`Payout initiated: ${payout.data.transaction_id}`);
    return payout.data;
  }

  // Generate expiry date (hours from now)
  function getExpiryDate(hours) {
    const date = new Date();
    date.setHours(date.getHours() + hours);
    return date.toISOString();
  }
  ```

  ```go Go wrap expandable theme={"system"}
  func initiatePayout(
      customerBalanceID string,
      billsAmount float64,
      reference string,
  ) (*blnkgo.Transaction, error) {
      client := getClient()

      // Calculate fee (0.5% with $1 minimum)
      feePercentage := 0.005
      minFee := 1.0
      calculatedFee := math.Max(billsAmount*feePercentage, minFee)
      totalAmount := billsAmount + calculatedFee

      payout, _, err := client.Transaction.Create(
          blnkgo.CreateTransactionRequest{
              ParentTransaction: blnkgo.ParentTransaction{
                  PreciseAmount: big.NewInt(int64(totalAmount * 100)),
                  Precision: 100,
                  Reference: reference,
                  Description: "Payout for bills",
                  Currency: "USD",
                  Source: customerBalanceID,
                  SkipQueue: true,
                  Destinations: []blnkgo.Source{
                      {
                          Identifier: "@BillsPayment",
                          Distribution: fmt.Sprintf("%g", billsAmount),
                      },
                      {
                          Identifier: "@Fees",
                          Distribution: fmt.Sprintf("%g", calculatedFee),
                      },
                  },
                  MetaData: blnkgo.MetaData{
                      "payout_status": "pending",
                      "fee_amount": calculatedFee,
                  },
              },
              Inflight: true,
              InflightExpiryDate: getExpiryDate(24),
          },
      )
      if err != nil {
          return nil, err
      }

      fmt.Printf("Payout initiated: %s\n", payout.TransactionID)
      return payout, nil
  }

  func getExpiryDate(hours int) string {
      return time.Now().UTC().Add(time.Duration(hours) * time.Hour).Format(time.RFC3339)
  }
  ```

  ```python Python wrap expandable theme={"system"}
  from datetime import datetime, timedelta

  def initiate_payout(customer_balance_id, bills_amount, reference):
      # Calculate fee (0.5% with $1 minimum)
      fee_percentage = 0.005
      min_fee = 1
      calculated_fee = max(bills_amount * fee_percentage, min_fee)
      total_amount = bills_amount + calculated_fee

      payout = blnk.transactions.create({
          "precise_amount": total_amount * 100,
          "precision": 100,
          "reference": reference,
          "description": "Payout for bills",
          "currency": "USD",
          "source": customer_balance_id,
          "destinations": [
              {
                  "identifier": "@BillsPayment",
                  "distribution": f"{bills_amount}",
                  "narration": "Payout for bills",
              },
              {
                  "identifier": "@Fees",
                  "distribution": f"{calculated_fee}",
                  "narration": "Payout processing fee",
              },
          ],
          "inflight": True,
          "inflight_expiry_date": get_expiry_date(24),
          "meta_data": {
              "payout_status": "pending",
              "fee_amount": calculated_fee,
          },
          "skip_queue": True,
      })

      print(f"Payout initiated: {payout.data['transaction_id']}")
      return payout.data

  def get_expiry_date(hours):
      return (datetime.utcnow() + timedelta(hours=hours)).isoformat()
  ```

  ```java Java wrap expandable theme={"system"}
  double feePercentage = 0.005;
  double minFee = 1;
  double calculatedFee = Math.max(billsAmount * feePercentage, minFee);
  double totalAmount = billsAmount + calculatedFee;

  ApiResponse<JsonNode> payout = blnk.transactions().create(
      CreateTransactions.create()
          .preciseAmount(totalAmount * 100)
          .precision(100)
          .reference(reference)
          .description("Payout for bills")
          .currency("USD")
          .source(customerBalanceId)
          .destinations(List.of(
              MultipleSourcesT.create()
                  .identifier("@BillsPayment")
                  .distribution(String.valueOf(billsAmount))
                  .narration("Payout for bills"),
              MultipleSourcesT.create()
                  .identifier("@Fees")
                  .distribution(String.valueOf(calculatedFee))
                  .narration("Payout processing fee")
          ))
          .inflight(true)
          .inflightExpiryDate(Instant.now().plus(24, ChronoUnit.HOURS).toString())
          .metaData(Map.of(
              "payout_status", "pending",
              "fee_amount", calculatedFee
          ))
          .skipQueue(true)
  );

  System.out.println(
      "Payout initiated: " + payout.data().get("transaction_id").asText()
  );
  ```
</CodeGroup>

#### Commit based on feedback from payout provider

<CodeGroup>
  ```bash cURL wrap expandable theme={"system"}
  curl -X POST "http://localhost:5001/search/transactions" \
    -H "X-blnk-key: <api-key>" \
    -H "Content-Type: application/json" \
    -d '{
      "q": "payout_ref_001",
      "query_by": "reference",
      "filter_by": "status:=INFLIGHT"
    }'

  curl -X POST "http://localhost:5001/{transaction_id}/metadata" \
    -H "X-blnk-key: <api-key>" \
    -H "Content-Type: application/json" \
    -d '{
      "meta_data": {
        "payout_status": "confirmed",
        "provider_payout_id": "po_123456",
        "confirmation_time": "2024-12-21T01:36:46.997063436Z"
      }
    }'

  curl -X PUT "http://localhost:5001/transactions/inflight/{transaction_id}" \
    -H "X-blnk-key: <api-key>" \
    -H "Content-Type: application/json" \
    -d '{
      "status": "commit"
    }'
  ```

  ```typescript TypeScript wrap expandable theme={"system"}
  // Webhook handler for payment processor notifications
  async function handlePaymentWebhook(webhookBody) {
    const { 
      event_type, 
      payout_id, 
      status, 
      transaction_reference 
    } = webhookBody;
    
    if (event_type === 'payment.success') {

      // Find the transaction by reference
      const searchResult = await blnk.Search.search({
        q: transaction_reference,
        query_by: "reference",
        filter_by: "status:=INFLIGHT"
      }, "transactions");

      // Get the parent transaction (transaction_id connecting both transactions together)
      const transaction = searchResult.data.hits[0].document;
      const transactionId = transaction.parent_transaction;

      const baseUrl = process.env.BLNK_BASE_URL ?? 'http://localhost:5001';
      const apiKey = process.env.BLNK_API_KEY ?? '';
      const metaRes = await fetch(`${baseUrl}/${transactionId}/metadata`, {
        method: 'POST',
        headers: {
          'X-Blnk-Key': apiKey,
          'Content-Type': 'application/json',
        },
        body: JSON.stringify({
          meta_data: {
            payout_status: 'confirmed',
            provider_payout_id: payout_id,
            confirmation_time: new Date().toISOString(),
          },
        }),
      });
      if (!metaRes.ok) throw new Error(await metaRes.text());

      const commit = await blnk.Transactions.updateStatus(transactionId, {
        status: 'commit',
      });
      if (commit.status !== 200 || !commit.data) {
        throw new Error(commit.message ?? 'Failed to commit payout');
      }
      console.log(`Payout confirmed and committed: ${transactionId}`);
      return {
        processed: true,
        transaction_id: transactionId,
      };
    }
  }
  ```

  ```go Go wrap expandable theme={"system"}
  func handlePaymentWebhook(webhookBody map[string]interface{}) (map[string]interface{}, error) {
      client := getClient()

      eventType, _ := webhookBody["event_type"].(string)
      payoutID, _ := webhookBody["payout_id"].(string)
      transactionReference, _ := webhookBody["transaction_reference"].(string)

      if eventType == "payment.success" {
          searchResult, _, err := client.Search.SearchDocument(
              blnkgo.SearchParams{
                  Q: transactionReference,
                  QueryBy: "reference",
                  FilterBy: "status:=INFLIGHT",
              },
              blnkgo.Transactions,
          )
          if err != nil {
              return nil, err
          }

          transaction := searchResult.Hits[0].Document
          transactionID := transaction.ParentTransaction

          _, _, err = client.Metadata.UpdateMetadata(
              transactionID,
              blnkgo.UpdateMetaDataRequest{
                  MetaData: blnkgo.MetaData{
                      "payout_status": "confirmed",
                      "provider_payout_id": payoutID,
                      "confirmation_time": time.Now().UTC().Format(time.RFC3339),
                  },
              },
          )
          if err != nil {
              return nil, err
          }

          _, _, err = client.Transaction.Update(
              transactionID,
              blnkgo.UpdateStatus{
                  Status: "commit",
              },
          )
          if err != nil {
              return nil, err
          }

          fmt.Printf("Payout confirmed and committed: %s\n", transactionID)
          return map[string]interface{}{
              "processed": true,
              "transaction_id": transactionID,
          }, nil
      }

      return nil, nil
  }
  ```

  ```python Python wrap expandable theme={"system"}
  from datetime import datetime

  def handle_payment_webhook(webhook_body):
      event_type = webhook_body["event_type"]
      payout_id = webhook_body["payout_id"]
      transaction_reference = webhook_body["transaction_reference"]

      if event_type == "payment.success":
          search_result = blnk.search.search({
              "q": transaction_reference,
              "query_by": "reference",
              "filter_by": "status:=INFLIGHT",
          }, "transactions")

          transaction = search_result.data["hits"][0]["document"]
          transaction_id = transaction["parent_transaction"]

          blnk.metadata.update(transaction_id, {
              "meta_data": {
                  "payout_status": "confirmed",
                  "provider_payout_id": payout_id,
                  "confirmation_time": datetime.utcnow().isoformat(),
              },
          })

          commit = blnk.transactions.update_status(transaction_id, {
              "status": "commit",
          })
          if commit.status != 200 or not commit.data:
              raise Exception(commit.message or "Failed to commit payout")

          print(f"Payout confirmed and committed: {transaction_id}")
          return {
              "processed": True,
              "transaction_id": transaction_id,
          }
  ```

  ```java Java wrap expandable theme={"system"}
  ApiResponse<JsonNode> searchResult = blnk.search().search(
      SearchParams.create()
          .q(transaction_reference)
          .queryBy("reference")
          .filterBy("status:=INFLIGHT"),
      "transactions");
  ```
</CodeGroup>

#### How multiple destinations work

When using [Multiple Destinations](/transactions/multiple-destinations) in a transaction (like in our tutorial), Blnk automatically generates a separate transaction record for each distribution. These individual records are linked together with the `parent_transaction` parameter.

This structure organizes and connects all parts of a multi-destination transaction efficiently.

A standout feature of this system is how actions on the `parent_transaction` affect the entire group:

* Committing the `parent_transaction` automatically commits all associated distributions, confirming the full distribution.
* Voiding the `parent_transaction` automatically voids all distributions, canceling the entire distribution in one step.

This unified approach simplifies managing complex transactions involving multiple recipients.

### Path 2: Failed payouts

The payout is pending, but a failure message was received from the provider, resulting in the transaction being voided:

<CodeGroup>
  ```bash cURL wrap expandable theme={"system"}
  curl -X POST "http://localhost:5001/search/transactions" \
    -H "X-blnk-key: <api-key>" \
    -H "Content-Type: application/json" \
    -d '{
      "q": "payout_ref_001",
      "query_by": "reference",
      "filter_by": "status:=INFLIGHT"
    }'

  curl -X POST "http://localhost:5001/{transaction_id}/metadata" \
    -H "X-blnk-key: <api-key>" \
    -H "Content-Type: application/json" \
    -d '{
      "meta_data": {
        "payout_status": "failed",
        "provider_payout_id": "po_123456",
        "confirmation_time": "2024-12-21T01:36:46.997063436Z"
      }
    }'

  curl -X PUT "http://localhost:5001/transactions/inflight/{transaction_id}" \
    -H "X-blnk-key: <api-key>" \
    -H "Content-Type: application/json" \
    -d '{
      "status": "void"
    }'
  ```

  ```typescript TypeScript wrap expandable theme={"system"}
  async function handlePaymentWebhook(webhookBody) {
    const { 
      event_type, 
      payout_id, 
      status, 
      transaction_reference 
    } = webhookBody;
    
    if (event_type === 'payment.failed') {

      // Find the transaction by reference
      const searchResult = await blnk.Search.search({
        q: transaction_reference,
        query_by: "reference",
        filter_by: "status:=INFLIGHT"
      }, "transactions");

      // Get the parent transaction (transaction_id connecting both transactions together)
      const transaction = searchResult.data.hits[0].document;
      const transactionId = transaction.parent_transaction;

      const baseUrl = process.env.BLNK_BASE_URL ?? 'http://localhost:5001';
      const apiKey = process.env.BLNK_API_KEY ?? '';
      const metaRes = await fetch(`${baseUrl}/${transactionId}/metadata`, {
        method: 'POST',
        headers: {
          'X-Blnk-Key': apiKey,
          'Content-Type': 'application/json',
        },
        body: JSON.stringify({
          meta_data: {
            payout_status: 'failed',
            provider_payout_id: payout_id,
            confirmation_time: new Date().toISOString(),
          },
        }),
      });
      if (!metaRes.ok) throw new Error(await metaRes.text());

      const voidResult = await blnk.Transactions.updateStatus(transactionId, {
        status: 'void',
      });
      if (voidResult.status !== 200 || !voidResult.data) {
        throw new Error(voidResult.message ?? 'Failed to void payout');
      }
      console.log(`Payout cancelled: ${transactionId}`);
      return {
        processed: true,
        transaction_id: transactionId,
      };
    }
  }
  ```

  ```go Go wrap expandable theme={"system"}
  func handlePaymentWebhook(webhookBody map[string]interface{}) (map[string]interface{}, error) {
      client := getClient()

      eventType, _ := webhookBody["event_type"].(string)
      payoutID, _ := webhookBody["payout_id"].(string)
      transactionReference, _ := webhookBody["transaction_reference"].(string)

      if eventType == "payment.failed" {
          searchResult, _, err := client.Search.SearchDocument(
              blnkgo.SearchParams{
                  Q: transactionReference,
                  QueryBy: "reference",
                  FilterBy: "status:=INFLIGHT",
              },
              blnkgo.Transactions,
          )
          if err != nil {
              return nil, err
          }

          transaction := searchResult.Hits[0].Document
          transactionID := transaction.ParentTransaction

          _, _, err = client.Metadata.UpdateMetadata(
              transactionID,
              blnkgo.UpdateMetaDataRequest{
                  MetaData: blnkgo.MetaData{
                      "payout_status": "failed",
                      "provider_payout_id": payoutID,
                      "confirmation_time": time.Now().UTC().Format(time.RFC3339),
                  },
              },
          )
          if err != nil {
              return nil, err
          }

          _, _, err = client.Transaction.Update(
              transactionID,
              blnkgo.UpdateStatus{
                  Status: "void",
              },
          )
          if err != nil {
              return nil, err
          }

          fmt.Printf("Payout cancelled: %s\n", transactionID)
          return map[string]interface{}{
              "processed": true,
              "transaction_id": transactionID,
          }, nil
      }

      return nil, nil
  }
  ```

  ```python Python wrap expandable theme={"system"}
  from datetime import datetime

  def handle_payment_webhook(webhook_body):
      event_type = webhook_body["event_type"]
      payout_id = webhook_body["payout_id"]
      transaction_reference = webhook_body["transaction_reference"]

      if event_type == "payment.failed":
          search_result = blnk.search.search({
              "q": transaction_reference,
              "query_by": "reference",
              "filter_by": "status:=INFLIGHT",
          }, "transactions")

          transaction = search_result.data["hits"][0]["document"]
          transaction_id = transaction["parent_transaction"]

          blnk.metadata.update(transaction_id, {
              "meta_data": {
                  "payout_status": "failed",
                  "provider_payout_id": payout_id,
                  "confirmation_time": datetime.utcnow().isoformat(),
              },
          })

          void_result = blnk.transactions.update_status(transaction_id, {
              "status": "void",
          })
          if void_result.status != 200 or not void_result.data:
              raise Exception(void_result.message or "Failed to void payout")

          print(f"Payout cancelled: {transaction_id}")
          return {
              "processed": True,
              "transaction_id": transaction_id,
          }
  ```

  ```java Java wrap expandable theme={"system"}
  ApiResponse<JsonNode> searchResult = blnk.search().search(
      SearchParams.create()
          .q(transaction_reference)
          .queryBy("reference")
          .filterBy("status:=INFLIGHT"),
      "transactions");
  ```
</CodeGroup>

### Path 2: Successful payout but reversed

The payout was initially successful, with confirmation received from the provider, but the funds were reversed by the provider due to an inability to settle the transaction:

<CodeGroup>
  ```bash cURL wrap expandable theme={"system"}
  curl -X POST "http://localhost:5001/refund-transaction/{transaction_id}" \
    -H "X-blnk-key: <api-key>"

  curl -X POST "http://localhost:5001/{refund_id}/metadata" \
    -H "X-blnk-key: <api-key>" \
    -H "Content-Type: application/json" \
    -d '{
      "meta_data": {
        "refund_reason": "Can't complete payout"
      }
    }'
  ```

  ```typescript TypeScript wrap expandable theme={"system"}
  async function refundPayout(transactionId, reason = "Can't complete payout") {
    
    // Process the refund
    const refund = await blnk.Transactions.refund(transactionId);
    
    const baseUrl = process.env.BLNK_BASE_URL ?? 'http://localhost:5001';
    const apiKey = process.env.BLNK_API_KEY ?? '';
    const metaRes = await fetch(`${baseUrl}/${refund.data.refund_id}/metadata`, {
      method: 'POST',
      headers: {
        'X-Blnk-Key': apiKey,
        'Content-Type': 'application/json',
      },
      body: JSON.stringify({
        meta_data: { refund_reason: reason },
      }),
    });
    if (!metaRes.ok) throw new Error(await metaRes.text());

    console.log(`Payout refunded: ${refund.data.refund_id}`);
    return refund.data;
  }
  ```

  ```go Go wrap expandable theme={"system"}
  func refundPayout(transactionID, reason string) (*blnkgo.Transaction, error) {
      client := getClient()
      if reason == "" {
          reason = "Can't complete payout"
      }

      refund, _, err := client.Transaction.Refund(transactionID)
      if err != nil {
          return nil, err
      }

      _, _, err = client.Metadata.UpdateMetadata(
          refund.TransactionID,
          blnkgo.UpdateMetaDataRequest{
              MetaData: blnkgo.MetaData{
                  "refund_reason": reason,
              },
          },
      )
      if err != nil {
          return nil, err
      }

      fmt.Printf("Payout refunded: %s\n", refund.TransactionID)
      return refund, nil
  }
  ```

  ```python Python wrap expandable theme={"system"}
  def refund_payout(transaction_id, reason="Can't complete payout"):
      # Process the refund
      refund = blnk.transactions.refund(transaction_id)

      blnk.metadata.update(refund.data["refund_id"], {
          "meta_data": {
              "refund_reason": reason,
          },
      })

      print(f"Payout refunded: {refund.data['refund_id']}")
      return refund.data
  ```

  ```java Java wrap expandable theme={"system"}
  ApiResponse<JsonNode> refund = blnk.transactions().refund(transactionId);
  ```
</CodeGroup>

***

## Conclusion

You now have a basic deposits and payouts system powered by Blnk Ledger. It efficiently processes transactions while ensuring accurate records.

The system is flexible and can be expanded with features like:

* Automated handling of failed transactions
* Custom withdrawal limits and approval workflows
* Support for multiple funding sources
* Real-time transaction monitoring and alerts

With Blnk’s transaction system, you can track and manage all money movements, building a solid foundation for your financial operations.

***

<CtaCallout title="Need help with your use case?" href="https://blnkfinance.com/contact/us?utm_source=blnk_docs&utm_medium=documentation&utm_campaign=home%2Finstall" buttonLabel="Speak with us" trackingEvent="clicked_pro_support">
  Get dedicated support for architecture reviews, integration planning, ledger workflows, and production deployment.
</CtaCallout>

***

<RelatedTopics
  items={[
{ title: "Internal balances", href: "/balances/internal-balances" },
{ title: "Record a transaction", href: "/transactions/introduction" },
{ title: "Overdrafts", href: "/transactions/overdrafts" },
]}
/>
