Webhook events for internal transfers between customers, including transfers across organizations.
Events
| Event Name | Status | Description |
|---|---|---|
transfer.completed | COMPLETED | Internal transfer completed successfully |
transfer.completed
Triggered when an internal transfer between two customers completes successfully. The customers can belong to the same organization or different organizations.
Important: Each completed transfer generates an
INevent for the receiving customer's organization and anOUTevent for the sending customer's organization. Delivery depends on the subscriptions configured by each organization. One endpoint receives both directions only when both customers belong to the same organization and its subscription matches both events. For a cross-organization transfer, each organization normally receives only its own direction; do not wait for both to determine completion. Make your handler idempotent ontransaction_id+direction.
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 | Event recipients |
|---|---|---|---|
Customer-to-customer transfer via the OpenAPI Create Transfer endpoint | Your application | Sender → Recipient | Sender and receiver, under their respective subscriptions |
| Customer-to-customer transfer via the 1Money admin console | Operations team | Sender → Recipient | Sender and receiver, under their respective subscriptions |
| Auto-sweep on a sub-account deposit (when auto-sweep is enabled for your organization) | System | Sub-account → Main account | Sender and receiver, under their respective subscriptions |
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 | Sender and receiver, under their respective subscriptions |
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",
"from_account_id": "BZ-SEND-1234",
"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: Each direction has a stable
resource.id. The sender delivery uses a distinct UUID. Treat resource IDs as opaque. Usedata.transaction_idwhen calling Retrieve Transfer.
{
"event_name": "transfer.completed",
"resource": {
"type": "transfers",
"id": "604230b2-5ba6-5413-b58f-4b9803dd73bb",
"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",
"to_account_id": "BZ-RECV-1234",
"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 | null | both | Account ID of the customer, when available |
transaction_id | string | both | Unique identifier for the transfer; matches the transaction_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 |
from_account_id | string | null | IN only | Account ID of the sending customer, when available |
to_account_id | string | null | OUT only | Account ID of the receiving customer, when available |
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 |
Participant fields
The customer_id and account_id identify the customer addressed by this delivery. For an IN event, from_customer_id and from_account_id identify the sender. For an OUT event, to_customer_id and to_account_id identify the receiver. The counterparty belongs to another organization for a cross-organization transfer; its presence in the payload does not mean your endpoint receives that party's event.
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",
"from_account_id": "BZ-CUST-A123",
"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",
"to_account_id": "BZ-CUST-B123",
"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.
