Webhook events for internal transfers between customers within your organization.
Events
| Event Name | Status | Description |
|---|---|---|
transfer.completed | COMPLETED | Internal transfer completed successfully |
transfer.completed
Triggered when an internal transfer between two customers in your organization is completed successfully.
Important: Each completed transfer fires two webhook deliveries — one to the receiving customer (
direction=IN) and one to the sending customer (direction=OUT). When both customers belong to the same organization (which is always the case for internal transfers), your registered webhook endpoint will receive both events for the sametransaction_id. Your handler should be idempotent ontransaction_id+directionto avoid double-processing.
When this event fires
The same transfer.completed event covers all internal transfer flows. Use the direction, from_customer_id, and to_customer_id fields to distinguish the participants:
| Trigger | Initiator | Funds flow | Both webhooks delivered |
|---|---|---|---|
Customer-to-customer transfer via the OpenAPI Create Transfer endpoint | Your application | Sender → Recipient | ✅ |
| Customer-to-customer transfer via the 1Money admin console | Operations team | Sender → Recipient | ✅ |
| Auto-sweep on a sub-account deposit (when auto-sweep is enabled for your organization) | System | Sub-account → Main account | ✅ |
Prefunding withdrawal — initial funding transfer when calling Create Withdrawal with mode=prefunding | Your application | Funder (main) → Sub-account on whose behalf the withdrawal is created | ✅ |
Compensating reversals (e.g. internal rollback when a prefunded withdrawal fails) do not emit
transfer.completed. They are reflected only in the underlying withdrawal status events.
Payload — receiver delivery (direction=IN)
direction=IN){
"event_name": "transfer.completed",
"resource": {
"type": "transfers",
"id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"idempotency_key": "678160c6-ca41-40ee-bfe7-35057ade157d"
},
"data": {
"status": "COMPLETED",
"customer_id": "cus_receiver_id",
"account_id": "BZ-RECV-1234",
"transaction_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"direction": "IN",
"from_customer_id": "cus_sender_id",
"asset": "USDC",
"amount": "1000.00",
"memorandum": "Payment for invoice #12345",
"created_at": "2024-01-22T12:00:00.000Z",
"modified_at": "2024-01-22T12:00:00.000Z"
}
}Payload — sender delivery (direction=OUT)
direction=OUT)Note: The
resource.idfor the sender delivery appends:OUTto the transfer ID. Usedata.transaction_id(notresource.id) when calling the Retrieve Transfer API.
{
"event_name": "transfer.completed",
"resource": {
"type": "transfers",
"id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"idempotency_key": "678160c6-ca41-40ee-bfe7-35057ade157d"
},
"data": {
"status": "COMPLETED",
"customer_id": "cus_sender_id",
"account_id": "BZ-SEND-1234",
"transaction_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"direction": "OUT",
"to_customer_id": "cus_receiver_id",
"asset": "USDC",
"amount": "1000.00",
"memorandum": "Payment for invoice #12345",
"created_at": "2024-01-22T12:00:00.000Z",
"modified_at": "2024-01-22T12:00:00.000Z"
}
}Fields
| Field | Type | Direction | Description |
|---|---|---|---|
status | string | both | Always COMPLETED for this event |
customer_id | string | both | The customer this delivery is addressed to (receiver for IN, sender for OUT) |
account_id | string | both | Account ID of the customer |
transaction_id | string | both | Unique identifier for the transfer; matches the id returned by Create Transfer |
direction | string | both | IN for the recipient delivery, OUT for the sender delivery |
from_customer_id | string | IN only | The sending customer's ID |
to_customer_id | string | OUT only | The receiving customer's ID |
asset | string | both | Asset code (e.g. USDC, USD, BTC, ETH) |
amount | string | both | Transfer amount as a decimal string |
memorandum | string | null | both | Optional memo provided by the sender; null when no memo was supplied |
created_at | string | both | ISO 8601 timestamp (millisecond precision, UTC) when the transfer record was created |
modified_at | string | both | ISO 8601 timestamp (millisecond precision, UTC) when the transfer record was last updated |
Payload Comparison
The transfer webhook payload is richer than other transaction webhooks because it identifies both counterparties:
| Field | Transfers | Deposits / Withdrawals / Conversions |
|---|---|---|
asset | ✅ Included | ❌ Not included |
amount | ✅ Included | ❌ Not included |
from_customer_id | ✅ Included (IN deliveries) | N/A |
to_customer_id | ✅ Included (OUT deliveries) | N/A |
direction | ✅ Included | N/A |
memorandum | ✅ Included | N/A |
modified_at | ✅ Included | ✅ Included |
This design lets both counterparties immediately understand the transfer details without making additional API calls.
Example Use Cases
Scenario 1 — Payment notification on the recipient side
When Customer A sends 1,000 USDC to Customer B, Customer B receives the IN delivery:
{
"data": {
"direction": "IN",
"customer_id": "cus_customer_b",
"account_id": "BZ-CUST-B123",
"from_customer_id": "cus_customer_a",
"asset": "USDC",
"amount": "1000.00",
"memorandum": "Invoice #INV-2024-001"
}
}Scenario 2 — Outgoing confirmation on the sender side
For the same transfer, Customer A receives the OUT delivery:
{
"data": {
"direction": "OUT",
"customer_id": "cus_customer_a",
"account_id": "BZ-CUST-A123",
"to_customer_id": "cus_customer_b",
"asset": "USDC",
"amount": "1000.00",
"memorandum": "Invoice #INV-2024-001"
}
}Use the OUT event to confirm that an outbound transfer initiated by your system has been settled, without polling the API.
Scenario 3 — Auto-sweep on sub-account deposit
When auto-sweep is enabled and a deposit lands on a sub-account, the system automatically transfers the funds to the main account. Both deliveries are emitted with from_customer_id = sub-account and to_customer_id = main account. The memorandum will reference the originating deposit order ID, e.g. Auto transfer from sub-account deposit dep_xxx.
Scenario 4 — Prefunding withdrawal initial transfer
When you call Create Withdrawal with mode=prefunding and on_behalf_of=<sub-account>, the funder (main account) transfers the withdrawal amount to the sub-account before the external payout begins. This produces a transfer.completed event with from_customer_id = funder and to_customer_id = sub-account, so your reconciliation system can correctly attribute the internal funding leg.
Idempotency & deduplication
Because both deliveries share the same transaction_id, your webhook handler should:
- Use
(transaction_id, direction)as the deduplication key, nottransaction_idalone. - Treat the
INandOUTdeliveries as two independent ledger updates — they describe the same transfer from each counterparty's perspective.
Retrieving full transfer details
To fetch the complete transfer record (including fees, status history, and timestamps), call the Retrieve Transfer endpoint with the transaction_id.
