Transactions

Commit inflight transactions

Apply one hold, many holds, or every leg in a split or batch.

After you create an inflight transaction, commit it when the main balance should change.

Commit clears the inflight balance fields and moves the reserved amount into the main balances.

Scenarios when inflight transactions are finished processing

Commit one transaction

Commit the full amount

Call Update inflight with the inflight transaction id and "status": "commit".

PUT /transactions/inflight/{transaction_id}
curl -X PUT "http://YOUR_BLNK_INSTANCE_URL/transactions/inflight/{transaction_id}" \
  -H "X-blnk-key: <api-key>" \
  -H "Content-Type: application/json" \
  -d '{
    "status": "commit"
  }'
const response = await blnk.Transactions.updateStatus(
  '{transaction_id}',
  {
    status: 'commit',
  },
);
transaction, _, _ := client.Transaction.Update(
    "{transaction_id}",
    blnkgo.UpdateStatus{
        Status: blnkgo.InflightStatusCommit,
    },
)
response = blnk.transactions.update_status(
  "{transaction_id}",
  {
    "status": "commit",
  },
)
ApiResponse<JsonNode> response = blnk.transactions().updateStatus(
    "{transaction_id}",
    UpdateTransactionStatus.create()
        .status("commit"));
{
  "transaction_id": "txn_6164573b-6cc8-45a4-ad2e-7b4ba6a60f7d",
  "status": "INFLIGHT",
  "queued": true
}

queued: true means the commit is waiting on a worker, not that the main balance has changed.

With skip_queue: true, the same call returns the APPLIED child and sets parent_transaction to the inflight id.

Continuing from the earlier example, a full commit of the $100 hold looks like this:

balance_idMain balanceInflight balanceInflight creditInflight debit
balance_A100000
balance_B100000

Commit part of the amount

Set precise_amount to the amount you want to commit, in its smallest unit. The rest stays INFLIGHT.

You can commit more than once. The total cannot exceed the original inflight amount.

A $100 hold can take three $30 commits ($90). A fourth $30 commit fails because $120 is more than the original $100.

curl -X PUT "http://YOUR_BLNK_INSTANCE_URL/transactions/inflight/{transaction_id}" \
  -H "X-blnk-key: <api-key>" \
  -H "Content-Type: application/json" \
  -d '{
    "status": "commit",
    "precise_amount": 4000
  }'
const response = await blnk.Transactions.updateStatus(
  '{transaction_id}',
  {
    status: 'commit',
    precise_amount: 4000,
  },
);
transaction, _, _ := client.Transaction.Update(
    "{transaction_id}",
    blnkgo.UpdateStatus{
        Status: blnkgo.InflightStatusCommit,
        PreciseAmount: big.NewInt(4000),
    },
)
response = blnk.transactions.update_status(
  "{transaction_id}",
  {
    "status": "commit",
    "precise_amount": 4000,
  },
)
ApiResponse<JsonNode> response = blnk.transactions().updateStatus(
    "{transaction_id}",
    UpdateTransactionStatus.create()
        .status("commit")
        .preciseAmount(4000));

A partial commit of $40 from the $100 hold leaves the remaining $60 inflight:

balance_idMain balanceInflight balanceInflight creditInflight debit
balance_A160-60060
balance_B4060600

Void releases whatever is still held. It does not reverse amount you already committed.

Commit the full amount

Open the inflight transaction. Commit completes it.

Commit and void on a transaction

Select Commit transaction, or press C. Leave Amount to commit empty, then confirm.

Commit transaction modal

A new linked transaction is created with status APPLIED.

Commit part of the amount

Enter the amount in Amount to commit, then confirm. The rest stays inflight.

Leave Amount to commit empty on the last commit. That applies whatever is still held.

Commit multiple inflights

Commit the holds

Use Bulk commit inflight for holds you created one by one. Each bulk commit request accepts at most 100 items.

POST /transactions/inflight/bulk/commit
curl -X POST "http://YOUR_BLNK_INSTANCE_URL/transactions/inflight/bulk/commit" \
  -H "X-blnk-key: <api-key>" \
  -H "Content-Type: application/json" \
  -d '{
    "transactions": [
      {
        "transaction_id": "txn_f482a1b3-6c2d-4e89-a17b-3d5e8f2a1c94"
      },
      {
        "transaction_id": "txn_c5d9e2a1-7b4f-4a3c-9e8d-1f6a2b4c8d30",
        "precise_amount": 4000
      }
    ]
  }'
const response = await blnk.Transactions.bulkCommitInflight({
  transactions: [
    {
      transaction_id: 'txn_f482a1b3-6c2d-4e89-a17b-3d5e8f2a1c94',
    },
    {
      transaction_id: 'txn_c5d9e2a1-7b4f-4a3c-9e8d-1f6a2b4c8d30',
      precise_amount: 4000,
    },
  ],
});
result, _, _ := client.Transaction.BulkCommitInflight(blnkgo.BulkCommitInflightRequest{
    Transactions: []blnkgo.BulkCommitInflightItem{
        {
            TransactionID: "txn_f482a1b3-6c2d-4e89-a17b-3d5e8f2a1c94",
        },
        {
            TransactionID: "txn_c5d9e2a1-7b4f-4a3c-9e8d-1f6a2b4c8d30",
            PreciseAmount: big.NewInt(4000),
        },
    },
})
response = blnk.transactions.bulk_commit_inflight({
  "transactions": [
    {
      "transaction_id": "txn_f482a1b3-6c2d-4e89-a17b-3d5e8f2a1c94",
    },
    {
      "transaction_id": "txn_c5d9e2a1-7b4f-4a3c-9e8d-1f6a2b4c8d30",
      "precise_amount": 4000,
    },
  ],
})
ApiResponse<JsonNode> response = blnk.transactions().bulkCommitInflight(
    BulkCommitInflightRequest.create()
        .transactions(List.of(
            BulkCommitInflightItem.create()
            .transactionId("txn_f482a1b3-6c2d-4e89-a17b-3d5e8f2a1c94"),
            BulkCommitInflightItem.create()
            .transactionId("txn_c5d9e2a1-7b4f-4a3c-9e8d-1f6a2b4c8d30")
            .preciseAmount(4000)
        )));

Read the result

One failed item does not block the rest.

StatusCodeMeaning
queuedQUEUEDNew commit job added.
queuedALREADY_QUEUEDAlready queued. The duplicate is skipped.
{
  "succeeded": 2,
  "failed": 0,
  "results": [
    {
      "transaction_id": "txn_f482a1b3-6c2d-4e89-a17b-3d5e8f2a1c94",
      "status": "queued",
      "code": "QUEUED"
    },
    {
      "transaction_id": "txn_c5d9e2a1-7b4f-4a3c-9e8d-1f6a2b4c8d30",
      "status": "queued",
      "code": "ALREADY_QUEUED"
    }
  ]
}

QUEUED and ALREADY_QUEUED both count toward succeeded. Set skip_queue: true to process each item in the request. Those results use succeeded or failed instead of queued.

Commit a split or a bulk batch

Choose the parent id

A split uses the root transaction_id. A bulk batch uses batch_id. That id commits every pending leg.

Commit the split or batch

Call Update single inflight with that id and "status": "commit". Do not send that id to Bulk commit inflight.

PUT /transactions/inflight/{parent_id}
curl -X PUT "http://YOUR_BLNK_INSTANCE_URL/transactions/inflight/{parent_id}" \
  -H "X-blnk-key: <api-key>" \
  -H "Content-Type: application/json" \
  -d '{
    "status": "commit"
  }'
const response = await blnk.Transactions.updateStatus(
  '{parent_id}',
  {
    status: 'commit',
  },
);
transaction, _, _ := client.Transaction.Update(
    "{parent_id}",
    blnkgo.UpdateStatus{
        Status: blnkgo.InflightStatusCommit,
    },
)
response = blnk.transactions.update_status(
  "{parent_id}",
  {
    "status": "commit",
  },
)
ApiResponse<JsonNode> response = blnk.transactions().updateStatus(
    "{parent_id}",
    UpdateTransactionStatus.create()
        .status("commit"));
{
  "transaction_id": "txn_0b59f6e6-6c4a-4efa-915c-526f77ef61ab",
  "status": "INFLIGHT",
  "queued": true
}

On the default queue, the response stays INFLIGHT with queued: true. Every leg and its later APPLIED child shares meta_data.QUEUED_PARENT_TRANSACTION.

With skip_queue: true, the response reflects the first leg processed. Each leg’s APPLIED child points at that leg through parent_transaction, so check each leg on its own.

Verify the outcome

You can confirm a commit or a void in two ways. Listen for the event, or filter the child transactions in the database.

Listen for the event

Blnk sends transaction.applied when a commit finishes, and transaction.void when a void finishes.

On the default queue, match meta_data.QUEUED_PARENT_TRANSACTION on the child to the inflight id. If the request skipped the queue, match parent_transaction to that id.

{
  "event": "transaction.applied",
  "data": {
    "transaction_id": "txn_e0f5ab98-bb87-4b09-aefe-839ddb11598b",
    "parent_transaction": "txn_6164573b-6cc8-45a4-ad2e-7b4ba6a60f7d",
    "precise_amount": 20000,
    "amount": 200,
    "precision": 100,
    "source": "bln_7f91a1ae-6073-4b7a-952c-23abf94a6634",
    "destination": "bln_59b83b9c-842c-427f-91eb-43cdeaf5c01a",
    "reference": "ref_001adcfgf_q",
    "currency": "GBP",
    "description": "For vacation",
    "status": "APPLIED",
    "inflight": false,
    "created_at": "2025-02-23T09:43:49.623494+01:00",
    "meta_data": {
      "QUEUED_PARENT_TRANSACTION": "txn_6164573b-6cc8-45a4-ad2e-7b4ba6a60f7d",
      "inflight": true
    }
  }
}
{
  "event": "transaction.void",
  "data": {
    "transaction_id": "txn_c3d4e5f6-a7b8-9012-cdef-345678901234",
    "parent_transaction": "txn_6164573b-6cc8-45a4-ad2e-7b4ba6a60f7d",
    "precise_amount": 6000,
    "amount": 60,
    "precision": 100,
    "source": "bln_7f91a1ae-6073-4b7a-952c-23abf94a6634",
    "destination": "bln_59b83b9c-842c-427f-91eb-43cdeaf5c01a",
    "reference": "ref_e7d61840-ecf7-45c1-a0b2-41687c7691f7_q",
    "currency": "GBP",
    "description": "For vacation",
    "status": "VOID",
    "inflight": false,
    "created_at": "2025-02-23T09:45:03.332221+01:00",
    "meta_data": {
      "QUEUED_PARENT_TRANSACTION": "txn_6164573b-6cc8-45a4-ad2e-7b4ba6a60f7d",
      "inflight": true
    }
  }
}

Filter the database

Use the Filter API to read the children from the database. A finished commit has status APPLIED. A finished void has status VOID.

Filter meta_data.QUEUED_PARENT_TRANSACTION for the inflight id, and keep children whose status is APPLIED or VOID. Each child’s parent_transaction points at the queued commit or void, so the shared key is QUEUED_PARENT_TRANSACTION.

curl -X POST "http://YOUR_BLNK_INSTANCE_URL/transactions/filter" \
  -H "X-blnk-key: <api-key>" \
  -H "Content-Type: application/json" \
  -d '{
    "filters": [
      {
        "field": "meta_data.QUEUED_PARENT_TRANSACTION",
        "operator": "eq",
        "value": "<inflight_transaction_id>"
      },
      {
        "field": "status",
        "operator": "in",
        "values": ["APPLIED", "VOID"]
      }
    ]
  }'
const response = await blnk.Search.filter(
  {
    filters: [
      {
        field: 'meta_data.QUEUED_PARENT_TRANSACTION',
        operator: 'eq',
        value: '<inflight_transaction_id>',
      },
      {
        field: 'status',
        operator: 'in',
        values: ['APPLIED', 'VOID'],
      },
    ],
  },
  'transactions',
);
results, _, _ := client.Transaction.Filter(blnkgo.FilterParams{
    Filters: []blnkgo.Filter{
        {
            Field: "meta_data.QUEUED_PARENT_TRANSACTION",
            Operator: blnkgo.OpEqual,
            Value: "<inflight_transaction_id>",
        },
        {
            Field: "status",
            Operator: blnkgo.OpIn,
            Values: []interface{}{"APPLIED", "VOID"},
        },
    },
})
response = blnk.search.filter(
  {
    "filters": [
      {
        "field": "meta_data.QUEUED_PARENT_TRANSACTION",
        "operator": "eq",
        "value": "<inflight_transaction_id>",
      },
      {
        "field": "status",
        "operator": "in",
        "values": ["APPLIED", "VOID"],
      },
    ],
  },
  "transactions",
)
ApiResponse<JsonNode> response = blnk.search().filter(
    FilterParams.create()
        .filters(List.of(
            FilterCondition.create()
            .field("meta_data.QUEUED_PARENT_TRANSACTION")
            .operator("eq")
            .value("<inflight_transaction_id>"),
            FilterCondition.create()
            .field("status")
            .operator("in")
            .values(List.of("APPLIED", "VOID"))
        )),
    "transactions");
{
  "data": [
    {
      "transaction_id": "txn_b2c3d4e5-f6a7-8901-bcde-f23456789012",
      "parent_transaction": "txn_d8f4a291-3e7c-41b6-a05d-9c2e71b48f6a",
      "status": "APPLIED",
      "meta_data": {
        "QUEUED_PARENT_TRANSACTION": "txn_6164573b-6cc8-45a4-ad2e-7b4ba6a60f7d"
      }
    },
    {
      "transaction_id": "txn_c3d4e5f6-a7b8-9012-cdef-345678901234",
      "parent_transaction": "txn_4a9c0e75-1b62-483f-8d31-7f6a25e903d4",
      "status": "VOID",
      "meta_data": {
        "QUEUED_PARENT_TRANSACTION": "txn_6164573b-6cc8-45a4-ad2e-7b4ba6a60f7d"
      }
    }
  ]
}

Filter parent_transaction for the inflight id, and keep children whose status is APPLIED or VOID. The child links straight to that inflight transaction.

curl -X POST "http://YOUR_BLNK_INSTANCE_URL/transactions/filter" \
  -H "X-blnk-key: <api-key>" \
  -H "Content-Type: application/json" \
  -d '{
    "filters": [
      {
        "field": "parent_transaction",
        "operator": "eq",
        "value": "<inflight_transaction_id>"
      },
      {
        "field": "status",
        "operator": "in",
        "values": ["APPLIED", "VOID"]
      }
    ]
  }'
const response = await blnk.Search.filter(
  {
    filters: [
      {
        field: 'parent_transaction',
        operator: 'eq',
        value: '<inflight_transaction_id>',
      },
      {
        field: 'status',
        operator: 'in',
        values: ['APPLIED', 'VOID'],
      },
    ],
  },
  'transactions',
);
results, _, _ := client.Transaction.Filter(blnkgo.FilterParams{
    Filters: []blnkgo.Filter{
        {
            Field: "parent_transaction",
            Operator: blnkgo.OpEqual,
            Value: "<inflight_transaction_id>",
        },
        {
            Field: "status",
            Operator: blnkgo.OpIn,
            Values: []interface{}{"APPLIED", "VOID"},
        },
    },
})
response = blnk.search.filter(
  {
    "filters": [
      {
        "field": "parent_transaction",
        "operator": "eq",
        "value": "<inflight_transaction_id>",
      },
      {
        "field": "status",
        "operator": "in",
        "values": ["APPLIED", "VOID"],
      },
    ],
  },
  "transactions",
)
ApiResponse<JsonNode> response = blnk.search().filter(
    FilterParams.create()
        .filters(List.of(
            FilterCondition.create()
            .field("parent_transaction")
            .operator("eq")
            .value("<inflight_transaction_id>"),
            FilterCondition.create()
            .field("status")
            .operator("in")
            .values(List.of("APPLIED", "VOID"))
        )),
    "transactions");
{
  "data": [
    {
      "transaction_id": "txn_b2c3d4e5-f6a7-8901-bcde-f23456789012",
      "parent_transaction": "txn_6164573b-6cc8-45a4-ad2e-7b4ba6a60f7d",
      "status": "APPLIED",
      "meta_data": {
        "inflight": true
      }
    },
    {
      "transaction_id": "txn_c3d4e5f6-a7b8-9012-cdef-345678901234",
      "parent_transaction": "txn_6164573b-6cc8-45a4-ad2e-7b4ba6a60f7d",
      "status": "VOID",
      "meta_data": {
        "inflight": true
      }
    }
  ]
}

Schedule a commit

Set inflight_commit_date when you create the hold. Blnk reserves the funds immediately and commits them at that time.

Use this when funds need to be held now and released at a known future time, such as a hotel deposit that settles on check-in.

curl -X POST "http://YOUR_BLNK_INSTANCE_URL/transactions" \
  -H "X-blnk-key: <api-key>" \
  -H "Content-Type: application/json" \
  -d '{
    "precise_amount": 10000,
    "precision": 100,
    "reference": "ref_001adcfgf",
    "currency": "USD",
    "source": "bln_28edb3e5-c168-4127-a1c4-16274e7a28d3",
    "destination": "bln_ebcd230f-6265-4d4a-a4ca-45974c47f746",
    "description": "For vacation",
    "inflight": true,
    "inflight_commit_date": "2024-12-21T01:36:46+01:00"
  }'
const response = await blnk.Transactions.create({
  precise_amount: 10000,
  precision: 100,
  reference: 'ref_001adcfgf',
  currency: 'USD',
  source: 'bln_28edb3e5-c168-4127-a1c4-16274e7a28d3',
  destination: 'bln_ebcd230f-6265-4d4a-a4ca-45974c47f746',
  description: 'For vacation',
  inflight: true,
  inflight_commit_date: '2024-12-21T01:36:46+01:00',
});
transaction, _, _ := client.Transaction.Create(blnkgo.CreateTransactionRequest{
    ParentTransaction: blnkgo.ParentTransaction{
        PreciseAmount: big.NewInt(10000),
        Precision: 100,
        Reference: "ref_001adcfgf",
        Currency: "USD",
        Source: "bln_28edb3e5-c168-4127-a1c4-16274e7a28d3",
        Destination: "bln_ebcd230f-6265-4d4a-a4ca-45974c47f746",
        Description: "For vacation",
    },
    Inflight: true,
    InflightCommitDate: "2024-12-21T01:36:46+01:00",
})
response = blnk.transactions.create({
  "precise_amount": 10000,
  "precision": 100,
  "reference": "ref_001adcfgf",
  "currency": "USD",
  "source": "bln_28edb3e5-c168-4127-a1c4-16274e7a28d3",
  "destination": "bln_ebcd230f-6265-4d4a-a4ca-45974c47f746",
  "description": "For vacation",
  "inflight": True,
  "inflight_commit_date": "2024-12-21T01:36:46+01:00",
})
ApiResponse<JsonNode> response = blnk.transactions().create(
    CreateTransactions.create()
        .preciseAmount(10000)
        .precision(100)
        .reference("ref_001adcfgf")
        .currency("USD")
        .source("bln_28edb3e5-c168-4127-a1c4-16274e7a28d3")
        .destination("bln_ebcd230f-6265-4d4a-a4ca-45974c47f746")
        .description("For vacation")
        .inflight(true)
        .inflightCommitDate("2024-12-21T01:36:46+01:00"));
{
  "transaction_id": "txn_6164573b-6cc8-45a4-ad2e-7b4ba6a60f7d",
  "status": "INFLIGHT",
  "inflight": true,
  "inflight_commit_date": "2024-12-21T01:36:46+01:00"
}

If you also set inflight_expiry_date, the commit time has to be earlier than the expiry. Otherwise, Blnk voids the hold before the scheduled commit runs. See Schedule inflight expiry.

Error handling

Update inflight returns 400, 404, and 409 when a commit fails validation or conflicts with the current state.

On the default queue, Blnk rejects a second commit or void before enqueueing another job. With skip_queue: true, Blnk validates in the request and returns the error immediately.

CodeWhen it happens
GEN_CONFLICTA commit or void is already queued for this inflight transaction.
TXN_COMMIT_AMOUNT_EXCEEDEDThe partial commit exceeds the remaining inflight amount.
TXN_ALREADY_VOIDEDYou try to commit a voided inflight transaction.
TXN_NOT_INFLIGHTThe transaction is not INFLIGHT.
TXN_INVALID_STATUS_ACTIONstatus is not commit or void.
TXN_NOT_FOUNDThe transaction id does not exist, or it is not the split or batch parent.
{
  "error": "a commit or void is already queued for this transaction",
  "error_detail": {
    "code": "GEN_CONFLICT",
    "message": "a commit or void is already queued for this transaction"
  }
}

To resolve the error:

CodeWhat to do
GEN_CONFLICTWait for the queued job to finish, or verify the outcome before sending another request.
TXN_COMMIT_AMOUNT_EXCEEDEDLower precise_amount to the remaining inflight amount, or commit the full remainder.
TXN_ALREADY_VOIDEDTreat the hold as cancelled. Do not commit it.
TXN_NOT_INFLIGHTUse the original inflight id, not an APPLIED or VOID child.
TXN_INVALID_STATUS_ACTIONSet status to commit.
TXN_NOT_FOUNDFor a split or batch, use the parent id from create, not a leg or child id.

Bulk commit inflight returns 400 before any item is processed when the body is invalid. Per-item failures return 200 with failed entries in results. One failed item does not block the batch.

CodeWhen it happens
TXN_BULK_EMPTYtransactions is missing, empty, or the wrong shape.
TXN_BULK_LIMIT_EXCEEDEDMore than 100 items in one request.

With skip_queue: true, a failed item can return NOT_FOUND, ALREADY_COMMITTED, ALREADY_VOIDED, NOT_INFLIGHT, INVALID_AMOUNT, LOCKED, or INTERNAL_ERROR. See API error codes.