Settlement
Receive a notification with the full charge list when a settlement is finalized
When a batched settlement is finalized, kamiPay sends a settlement.settled webhook to your configured endpoint. The payload contains the aggregate amounts for the settlement and the full list of charges that were included in it, so you can reconcile each individual charge against your own system without an extra API round-trip. One webhook delivery per scheduled batch summarises the disbursement and lists every charge that was part of it.
This webhook is specific to the batched settlement program — it fires for both Fiwind batched and blockchain batched settlements. If you are on the legacy 1:1 mode you do not receive settlement.settled — each charge is reconciled individually through the per-charge charge webhook the moment it completes.
Blockchain batched settlements fire settlement.settled once the bulk USDT transfer is mined on-chain (there is no provider webhook on that path). There is no separate per-charge settlement webhook — each charge is acknowledged immediately by its per-charge done charge webhook, and the single settlement.settled closes the batch.
settlement.settled
Sent once per settlement when the underlying transfer is confirmed — provider confirmation for Fiwind batched, or the bulk transfer being mined for blockchain batched — and the settlement transitions to DONE.
The payload shape is identical for both modes, but the currencies and amounts differ. The Fiwind batched payload settles in ARS (with the USDT amount kamiPay moved to the provider on the source_* fields). The blockchain batched payload settles in USDt and carries no ARS fields — there is no requote, and each charge's settlement_amount is in USDt.
Both payloads also carry a refunds[] array listing the pay-in refunds that were deducted from this settlement (documented in full further down). It is always present — an empty array when there were none. When it is non-empty, source_amount is lower than source_net_price by the total deducted:
Σ charges[].settlement_amount − Σ refunds[].deduction_amount = source_amount{
"event": "settlement.settled",
"settlement_id": 12345,
"provider_settlement_id": "psid_8f3c1d2a9e",
"external_settlement_id": "0xa3f9b2c1e7d4865094bd28fa1c3e6b85907df42a3b9c1de80f5a672bc41e9d3f",
"amount": 99325.0,
"currency_id": 32,
"source_amount": 68.5,
"source_currency_id": 9999,
"source_net_price": 68.3,
"settled_at": "2026-05-13T15:00:42Z",
"charges": [
{
"kamipay_id": "txc_01j3t9fxqffrva9s8d1ekm4g3v",
"external_id": "merchant-order-aaa-11112",
"kamipay_request_id": "ptxr_01j3t9fxqffrva9s8d1ekm4g3w",
"charged_amount": 5.28,
"charged_currency": "BRL",
"settlement_amount": 29750.0,
"settlement_currency": "ARS"
},
{
"kamipay_id": "txc_01j4a2x6m1d8h6e5b3t7w1ku9k",
"external_id": "merchant-order-aaa-11113",
"kamipay_request_id": "ptxr_01j4a2x6m1d8h6e5b3t7w1ku9l",
"charged_amount": 7.04,
"charged_currency": "BRL",
"settlement_amount": 39575.0,
"settlement_currency": "ARS"
},
{
"kamipay_id": "txc_01j4c8e7n3p2q5r9w1m6b8y4hj",
"external_id": null,
"kamipay_request_id": "ptxr_01j4c8e7n3p2q5r9w1m6b8y4hk",
"charged_amount": 5.28,
"charged_currency": "BRL",
"settlement_amount": 29750.0,
"settlement_currency": "ARS"
}
],
"refunds": []
}{
"event": "settlement.settled",
"settlement_id": 67890,
"provider_settlement_id": "0x9c1de80f5a672bc41e9d3fa3f9b2c1e7d4865094bd28fa1c3e6b85907df42a3b",
"external_settlement_id": "0x9c1de80f5a672bc41e9d3fa3f9b2c1e7d4865094bd28fa1c3e6b85907df42a3b",
"amount": 57.5,
"currency_id": 9999,
"source_amount": 57.5,
"source_currency_id": 9999,
"source_net_price": 60.0,
"settled_at": "2026-05-13T15:00:42Z",
"charges": [
{
"kamipay_id": "txc_01j3t9fxqffrva9s8d1ekm4g3v",
"external_id": "merchant-order-aaa-11112",
"kamipay_request_id": "ptxr_01j3t9fxqffrva9s8d1ekm4g3w",
"charged_amount": 52.8,
"charged_currency": "BRL",
"settlement_amount": 10.0,
"settlement_currency": "USDt"
},
{
"kamipay_id": "txc_01j4a2x6m1d8h6e5b3t7w1ku9k",
"external_id": "merchant-order-aaa-11113",
"kamipay_request_id": "ptxr_01j4a2x6m1d8h6e5b3t7w1ku9l",
"charged_amount": 105.6,
"charged_currency": "BRL",
"settlement_amount": 20.0,
"settlement_currency": "USDt"
},
{
"kamipay_id": "txc_01j4c8e7n3p2q5r9w1m6b8y4hj",
"external_id": null,
"kamipay_request_id": "ptxr_01j4c8e7n3p2q5r9w1m6b8y4hk",
"charged_amount": 158.4,
"charged_currency": "BRL",
"settlement_amount": 30.0,
"settlement_currency": "USDt"
}
],
"refunds": [
{
"kamipay_refund_id": "rpi_5b7c2d4e6a8f0b1c3d5e7f90",
"kamipay_id": "txc_01j4a2x6m1d8h6e5b3t7w1ku9k",
"external_id": "merchant-order-aaa-11113",
"kamipay_request_id": "ptxr_01j4a2x6m1d8h6e5b3t7w1ku9l",
"refunded_amount": 13.2,
"refunded_currency": "BRL",
"deduction_amount": 2.5,
"deduction_currency": "USDt"
}
]
}The three charges add up to source_net_price (10 + 20 + 30 = 60), the refund
deducts 2.5, and amount / source_amount are the resulting 57.5 — the USDt
actually transferred to your wallet.
Telling the two apart: read currency_id / settlement_currency. The blockchain batched payload has no ARS anywhere — every amount is USDt — and the external_settlement_id is the on-chain hash of the single bulk transfer. With no refunds deducted, amount, source_amount, and source_net_price are one and the same USDt total, because there is no requote on this path.
Top-level fields
| Field | Type | Description |
|---|---|---|
event | string | Always "settlement.settled". |
settlement_id | integer | Unique identifier of the settlement (matches settlement_id returned by GET /v1/settlements). |
provider_settlement_id | string | null | Provider-side identifier for the transfer. |
external_settlement_id | string | null | Auxiliary external reference (e.g. originating vault transaction hash for off-chain settlements). |
amount | number | Amount actually delivered to your destination address, in currency_id units. |
currency_id | integer | Currency id of amount. |
source_amount | number | Amount kamiPay actually transferred to the provider for this settlement, in source_currency_id units. |
source_currency_id | integer | Currency id of source_amount. |
source_net_price | number | Gross accumulated value of the included charges on the source side, in source_currency_id units — charges only, before any refund deduction. For Fiwind batched, the difference against source_amount also reflects internal repricing absorbed by the settlement. For blockchain batched there is no requote, so the only thing that can separate the two is a refund deduction: with an empty refunds[], source_net_price equals source_amount (and amount). |
settled_at | datetime | ISO 8601 timestamp when the settlement was marked DONE. |
charges | array | All charges included in this settlement. See below. |
refunds | array | Pay-in refunds deducted from this settlement. Always present; an empty array when none were deducted. See below. |
charges[] fields
The charges[] array contains one item per charge included in the settlement, ordered by the time each charge was added to the settlement (oldest first).
| Field | Type | Description |
|---|---|---|
kamipay_id | string | The QR/transaction kamiPay identifier of the original charge (matches the value returned at QR creation and the kamipay_id on the corresponding charge webhook). |
external_id | string | null | Your own identifier for the charge, as you supplied it at checkout time. null if you did not provide one. This is the primary reconciliation key for the merchant — the same value appears on the per-charge done event of the charge webhook. |
kamipay_request_id | string | Pay-in request identifier (Pix id). |
charged_amount | number | Amount charged to the payor, typically BRL. |
charged_currency | string | Currency code (alpha3) of charged_amount, e.g. BRL. |
settlement_amount | number | null | The amount the charge was quoted at — the settlement-side promise, expressed in the currency the checkout quotes in. null only for charges with no quote linked. |
settlement_currency | string | null | Currency code (alpha3) of settlement_amount — the quote currency of the checkout quote layout (e.g. ARS, USDt, BRL). |
refunds[] fields
The refunds[] array contains one item per pay-in refund whose amount was subtracted from this settlement. A refund is deducted from the first settlement that runs after it is confirmed — which is usually not the settlement that contained the original charge, so the kamipay_id of a refund often refers to a charge you reconciled in an earlier batch.
| Field | Type | Description |
|---|---|---|
kamipay_refund_id | string | Identifier of the refund itself (rpi_…). |
kamipay_id | string | The kamiPay identifier of the original charge being refunded. |
external_id | string | null | Your own identifier for that original charge, as supplied at checkout time. null if you did not provide one. This is the reconciliation key — it matches the external_id you already recorded for the charge. |
kamipay_request_id | string | Pay-in request identifier (Pix id) of the original charge. |
refunded_amount | number | Amount returned to the payor, typically BRL. Matches the value you requested (or the full charge amount when you omitted it). |
refunded_currency | string | Currency code (alpha3) of refunded_amount, e.g. BRL. |
deduction_amount | number | Amount subtracted from this settlement, in source_currency_id units. Prorated at the original charge's own rate, so a full refund cancels the charge exactly. |
deduction_currency | string | Currency code (alpha3) of deduction_amount. |
If the pending refund deductions for a checkout add up to more than the charges waiting to be settled, no settlement is produced that cycle — a zero or negative transfer makes no sense. Both the charges and the refunds stay pending and are netted in a later cycle, once enough new charges have accumulated to absorb the deduction.
Reconciliation
The intended flow for merchants:
- As charges complete, you receive a
doneevent on the charge webhook for each one. Match it against your order usingexternal_idand record it as collected. - When the settlement is finalized you receive a single
settlement.settledwebhook. Thecharges[]array carries the sameexternal_idfor every charge you previously recorded. - Use
external_idto match each entry incharges[]back to your collected orders, and useamount(incurrency_idunits) to record the disbursement against your bank/wallet account. - If
refunds[]is non-empty, match each entry back byexternal_idtoo and record the deduction against that order. The originating charge was most likely settled in an earlier batch, so expect these to reference orders you have already reconciled.
Do not derive the disbursement by summing charges[] — that gives you the gross. amount is the net figure that hits your account, and the two differ by exactly Σ refunds[].deduction_amount.
The shape of charges[] exactly mirrors what GET /v1/settlements/{settlement_id} returns. If you ever miss a webhook delivery you can fetch the same data over REST and get the same reconciliation keys.
Signature validation, retries, and ordering
- Signature: every webhook is signed with the same scheme as the charge and pay webhooks. Always validate the signature before processing the payload.
- Idempotency:
settlement_idis the natural idempotency key. kamiPay may retry the delivery (e.g. on a non-2xx response from your endpoint) and you may receive the samesettlement.settledpayload more than once. Treat repeated deliveries with the samesettlement_idas no-ops after the first successful processing. - Ordering:
settlement.settledis fired after all of the per-chargedoneevents for the charges incharges[]. You may, however, receive asettlement.settledfor one settlement before alldoneevents for charges in a different settlement, so do not assume strict cross-settlement ordering. - Delivery channel: this event uses the same webhook endpoint and authentication configuration as your other kamiPay webhooks. No separate configuration is required.
Always validate the webhook signature using the method described in the Introduction before processing any webhook events.