Overview
Fintech products and payroll use cases frequently need to send value to many recipients in a single operation — disbursing salaries, paying out merchants, or distributing rewards. While POST /v1/transfers/batch exists in the API reference, the underlying batch processing logic — fan-out queueing, per-transfer status tracking, partial failure handling, and a batch-level summary endpoint — is not yet implemented. This issue covers the full batch transfer feature from queue worker to API response.
What needs to be built
internal/transfer/ — Batch service layer
-
BatchTransfer domain type in internal/domain/ — fields: id, org_id, status (pending | processing | completed | partially_failed | failed), total_count, succeeded_count, failed_count, created_at, settled_at
-
POST /v1/transfers/batch — accepts up to 100 transfer objects in one call:
- Validates each transfer item (source wallet, destination, asset, amount) before any are enqueued — reject the entire batch if any item fails validation
- Creates a
batch_transfers record and individual transactions records in a single DB transaction
- Enqueues one Asynq task per transfer with the batch ID attached
- Returns
202 Accepted with batch_id and per-item transfer_id list immediately
-
GET /v1/transfers/batch/:id — returns batch-level summary: status, total/succeeded/failed counts, and the full per-transfer status list
-
Background worker: processes each transfer task independently; on completion updates the parent batch aggregate counts atomically; marks batch as completed or partially_failed once all items settle
Webhooks
-
Emit batch.completed webhook when all transfers in a batch have settled
-
Emit batch.partially_failed when at least one transfer failed and at least one succeeded
-
Each individual transfer still emits transfer.settled / transfer.failed as normal
db/migrations/
-
New migration: batch_transfers table with the fields above
-
Add batch_id nullable foreign key column to existing transactions table
Acceptance criteria
Overview
Fintech products and payroll use cases frequently need to send value to many recipients in a single operation — disbursing salaries, paying out merchants, or distributing rewards. While
POST /v1/transfers/batchexists in the API reference, the underlying batch processing logic — fan-out queueing, per-transfer status tracking, partial failure handling, and a batch-level summary endpoint — is not yet implemented. This issue covers the full batch transfer feature from queue worker to API response.What needs to be built
internal/transfer/— Batch service layerBatchTransferdomain type ininternal/domain/— fields:id,org_id,status(pending|processing|completed|partially_failed|failed),total_count,succeeded_count,failed_count,created_at,settled_atPOST /v1/transfers/batch— accepts up to 100 transfer objects in one call:batch_transfersrecord and individualtransactionsrecords in a single DB transaction202 Acceptedwithbatch_idand per-itemtransfer_idlist immediatelyGET /v1/transfers/batch/:id— returns batch-level summary: status, total/succeeded/failed counts, and the full per-transfer status listBackground worker: processes each transfer task independently; on completion updates the parent batch aggregate counts atomically; marks batch as
completedorpartially_failedonce all items settleWebhooks
Emit
batch.completedwebhook when all transfers in a batch have settledEmit
batch.partially_failedwhen at least one transfer failed and at least one succeededEach individual transfer still emits
transfer.settled/transfer.failedas normaldb/migrations/New migration:
batch_transferstable with the fields aboveAdd
batch_idnullable foreign key column to existingtransactionstableAcceptance criteria
GET /v1/transfers/:idGET /v1/transfers/batch/:idis consistent with the sum of individual transfer statusesbatch.completedwebhook fires exactly once when the last transfer in the batch settles