Start here

0.10.5 Migration Guide

Decide whether bulk create should stay synchronous before you upgrade to Blnk v0.10.5.

This migration guide covers the breaking change introduced in Blnk v0.10.5 for bulk transactions. Bulk create now uses the queue by default instead of processing every item synchronously in the request. If you are upgrading from v0.10.4 or earlier and your app expects balances or final statuses to update before the bulk create response returns, review this guide before you upgrade.

If you run 0.10.5 or later on the queued bulk path, upgrade to Blnk Core 0.15.0 or later. From 0.15.0, Core migrations add expression indexes on meta_data->>'QUEUED_PARENT_TRANSACTION' and related queue hot paths. Without those indexes, commit, refund, and parent lookups can fall back to sequential scans and drain the queue slowly under load.

Prefer upgrading over staying on 0.10.5 – 0.14.x with a hand-applied index. If you cannot upgrade yet, create the index manually on your Postgres database:

CREATE INDEX CONCURRENTLY IF NOT EXISTS idx_transactions_queued_parent
ON blnk.transactions ((meta_data->>'QUEUED_PARENT_TRANSACTION'));

Core 0.15.0 applies an equivalent index as idx_txn_meta_queued_parent (partial, where the key is present) plus related queue indexes. After you upgrade, you do not need to keep a separate hand-created index unless your operator process requires it.

Breaking changes summary

  • From: Bulk create always processed items synchronously (skip_queue effectively true).
  • To: Bulk create defaults to skip_queue: false and enqueues each item.
  • Impact: A successful bulk response no longer guarantees that every child transaction is already APPLIED or INFLIGHT on balances. Parent linking and duplicate-reference behaviour also follow the queued path unless you opt out.

What changed

  1. Default queueing: Bulk create now respects skip_queue and defaults it to false. Before 0.10.5, Blnk forced synchronous processing inside the batch.
  2. Response meaning: On the default path, status: "applied" means the batch was successfully submitted, not that every child has already moved balances.
  3. Parent linking: With skip_queue: false, children store batch_id in meta_data.QUEUED_PARENT_TRANSACTION. With skip_queue: true, children set parent_transaction to batch_id.
  4. Duplicate references: On the queued path, duplicate references are deduplicated silently and the batch can still return 201. On skip_queue: true, duplicates fail the batch with 409.

See Bulk transactions for current behaviour.

Who is affected?

Review this change if your integration:

  • Reads balances or child transaction statuses immediately after bulk create
  • Treats bulk status: "applied" as proof that every item is final
  • Looks up batch children only by parent_transaction
  • Relies on synchronous 409 failures for duplicate references inside a bulk request

If none of these apply, you can upgrade without changing your bulk requests.

Migration options

Choose one path based on what your next step needs.

NeedWhat to do
Keep pre-0.10.5 synchronous behaviourSend "skip_queue": true on every bulk create
Adopt the new default queue pathOmit skip_queue or set "skip_queue": false, then confirm child outcomes asynchronously

Option 1: Keep synchronous processing

Add "skip_queue": true to restore the previous inline behaviour. Use this when the next step in your app needs the ledger outcome before the HTTP call returns (for example, show an updated balance, fail the request with the batch, or chain another post that depends on these balances).

curl -X POST "http://YOUR_BLNK_INSTANCE_URL/transactions/bulk" \
  -H "X-blnk-key: <api-key>" \
  -H "Content-Type: application/json" \
  -d '{
    "atomic": true,
    "inflight": false,
    "run_async": false,
    "skip_queue": true,
    "transactions": [
      {
        "amount": 1000,
        "precision": 100,
        "reference": "ref_f482a1b3-6c2d-4e89-a17b-3d5e8f2a1c94",
        "currency": "USD",
        "source": "@World",
        "destination": "bln_ebcd1a29-8673-4d31-b410-010add942bec",
        "description": "Payroll credit",
        "allow_overdraft": true
      },
      {
        "amount": 500,
        "precision": 100,
        "reference": "ref_c5d9e2a1-7b4f-4a3c-9e8d-1f6a2b4c8d30",
        "currency": "USD",
        "source": "bln_ebcd1a29-8673-4d31-b410-010add942bec",
        "destination": "bln_7b8a2c1d-4e5f-6a7b-8c9d-0e1f2a3b4c5d",
        "description": "Payroll split"
      }
    ]
  }'
const response = await blnk.Transactions.createBulk({
  atomic: true,
  inflight: false,
  run_async: false,
  skip_queue: true,
  transactions: [
    {
      amount: 1000,
      precision: 100,
      reference: 'ref_f482a1b3-6c2d-4e89-a17b-3d5e8f2a1c94',
      currency: 'USD',
      source: '@World',
      destination: 'bln_ebcd1a29-8673-4d31-b410-010add942bec',
      description: 'Payroll credit',
      allow_overdraft: true,
    },
    {
      amount: 500,
      precision: 100,
      reference: 'ref_c5d9e2a1-7b4f-4a3c-9e8d-1f6a2b4c8d30',
      currency: 'USD',
      source: 'bln_ebcd1a29-8673-4d31-b410-010add942bec',
      destination: 'bln_7b8a2c1d-4e5f-6a7b-8c9d-0e1f2a3b4c5d',
      description: 'Payroll split',
    },
  ],
});
result, _, _ := client.Transaction.CreateBulk(blnkgo.CreateBulkTransactionRequest{
    Atomic: true,
    Inflight: false,
    RunAsync: false,
    SkipQueue: true,
    Transactions: []blnkgo.CreateTransactionRequest{
        {
            Amount: 1000,
            Precision: 100,
            Reference: "ref_f482a1b3-6c2d-4e89-a17b-3d5e8f2a1c94",
            Currency: "USD",
            Source: "@World",
            Destination: "bln_ebcd1a29-8673-4d31-b410-010add942bec",
            Description: "Payroll credit",
            AllowOverdraft: true,
        },
        {
            Amount: 500,
            Precision: 100,
            Reference: "ref_c5d9e2a1-7b4f-4a3c-9e8d-1f6a2b4c8d30",
            Currency: "USD",
            Source: "bln_ebcd1a29-8673-4d31-b410-010add942bec",
            Destination: "bln_7b8a2c1d-4e5f-6a7b-8c9d-0e1f2a3b4c5d",
            Description: "Payroll split",
        },
    },
})

With skip_queue: true:

  • Child statuses and balances update in the same request when run_async is false.
  • Look up children with parent_transaction = batch_id.
  • Duplicate references fail the batch with 409.

Option 2: Adopt the queued default

Omit skip_queue or set "skip_queue": false. Then update your confirmation path:

  1. Save batch_id from the create response.
  2. Do not treat status: "applied" as final ledger success for every child.
  3. Confirm child outcomes by filtering on meta_data.QUEUED_PARENT_TRANSACTION, or wait for webhook events.
{
  "batch_id": "bulk_c62f200b-905f-4983-a349-cadd279234aa",
  "status": "applied",
  "transaction_count": 2
}

Migration checklist

  • Prefer upgrading to 0.15.0 or later so queue parent indexes are applied by migration.
  • If you must stay below 0.15.0 for now, create idx_transactions_queued_parent manually (see Recommended upgrade).
  • Decide per bulk call whether you need sync (skip_queue: true) or queue (skip_queue: false).
  • Update any code that assumes balances change, or that an external payout is safe, before the bulk create response returns.
  • Confirm child outcomes with webhooks or by filtering on meta_data.QUEUED_PARENT_TRANSACTION before you call external providers or release funds.
  • Update batch child lookups to use meta_data.QUEUED_PARENT_TRANSACTION on the default path, or parent_transaction when you set skip_queue: true.
  • Revisit duplicate-reference handling if you previously relied on synchronous 409 failures.
  • For large batches, prefer run_async: true and handle bulk webhooks.