# Escrow

Learn how to implement an escrow payment with Blnk.

An escrow payment is money a buyer sends to a seller that stays on hold until the sale is done. A commit releases it to the seller, and a void returns it to the buyer.

This tutorial shows how to implement that with Blnk.

<TutorialSteps>
  <TutorialStep title="Design the money movement">
    Before we start building, we need to define our [money movement](/ledgers/money-movement-map). This helps us outline how money moves from Alice to Bob.

    Alice is buying from Bob, and the payment stays on hold until the sale is done. She sends \$100.00 from her balance to his.

    The transaction is [inflight](/transactions/inflight/create-inflight), so neither balance changes until we commit or void it. A commit applies the \$100.00 to Bob, and a void drops the hold and leaves the money with Alice.

    <TutorialFrame plain>
      <Map src="https://map.blnkfinance.xyz/mrvNJgqIAD" title="Escrow money movement" />
    </TutorialFrame>
  </TutorialStep>

  <TutorialStep title="Create a ledger">
    Before we start recording transactions, we need to set up our [ledger](/ledgers/introduction) to track the buyer, the seller, and their balances.

    For our escrow example, we only support one currency, USD. Therefore, we'll create a ledger to manage all USD balances belonging to our customers.

    Create the ledger:

<TutorialCode>
<CodeGroup>

```bash cURL wrap
curl -X POST "{{CORE_URL}}/ledgers" \
  -H "X-blnk-key: {{CORE_SECRET_KEY}}" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "{{name}}",
    "meta_data": {
      "description": "{{description}}"
    }
  }'
```

```typescript TypeScript wrap
const response = await blnk.Ledgers.create({
  name: '{{name}}',
  meta_data: {
    description: '{{description}}',
  },
});
```

```go Go wrap
ledger, _, _ := client.Ledger.Create(blnkgo.CreateLedgerRequest{
    Name: "{{name}}",
    MetaData: blnkgo.MetaData{
        "description": "{{description}}",
    },
})
```

```python Python wrap
response = blnk.ledgers.create({
  "name": "{{name}}",
  "meta_data": {
    "description": "{{description}}",
  },
})
```

```java Java wrap
ApiResponse<JsonNode> response = blnk.ledgers().create(
    CreateLedger.create()
        .name("{{name}}")
        .metaData(Map.of(
            "description", "{{description}}"
        )));
```

</CodeGroup>
</TutorialCode>

    <TutorialFrame>
      <Tabs>
        <Tab title="Try it out">
          <TutorialForm id="create-ledger" method="POST" path="/ledgers" save="ledger_id">
            <TutorialInput name="name" label="Ledger name" value="Customer USD Ledger" />
            <TutorialInput name="description" label="Description" value="USD balances belonging to customers" group="meta_data" />
          </TutorialForm>
        </Tab>
        <Tab title="Response">
          <TutorialResponse />
        </Tab>
      </Tabs>
    </TutorialFrame>
  </TutorialStep>

  <TutorialStep title="Create the buyer">
    An [identity](/identities/introduction) is how we record Alice, the buyer. We pass her type, name, email, and phone number.

    Anything else we want to keep goes in [metadata](/search/metadata). We'll store her customer id there as `customer_id`.

    Create the buyer:

<CodeGroup>

```bash cURL wrap
curl -X POST "{{CORE_URL}}/identities" \
  -H "X-blnk-key: {{CORE_SECRET_KEY}}" \
  -H "Content-Type: application/json" \
  -d '{
    "identity_type": "individual",
    "first_name": "{{first_name}}",
    "last_name": "{{last_name}}",
    "email_address": "{{email_address}}",
    "phone_number": "{{phone_number}}",
    "meta_data": {
      "customer_id": "{{customer_id}}"
    }
  }'
```

```typescript TypeScript wrap
const response = await blnk.Identity.create({
  identity_type: 'individual',
  first_name: '{{first_name}}',
  last_name: '{{last_name}}',
  email_address: '{{email_address}}',
  phone_number: '{{phone_number}}',
  meta_data: {
    customer_id: '{{customer_id}}',
  },
});
```

```go Go wrap
identity, _, _ := client.Identity.Create(blnkgo.Identity{
    IdentityType: blnkgo.Individual,
    FirstName: "{{first_name}}",
    LastName: "{{last_name}}",
    EmailAddress: "{{email_address}}",
    PhoneNumber: "{{phone_number}}",
    MetaData: blnkgo.MetaData{
        "customer_id": "{{customer_id}}",
    },
})
```

```python Python wrap
response = blnk.identity.create({
  "identity_type": "individual",
  "first_name": "{{first_name}}",
  "last_name": "{{last_name}}",
  "email_address": "{{email_address}}",
  "phone_number": "{{phone_number}}",
  "meta_data": {
    "customer_id": "{{customer_id}}",
  },
})
```

```java Java wrap
ApiResponse<JsonNode> response = blnk.identity().create(
    IdentityData.create()
        .identityType("individual")
        .firstName("{{first_name}}")
        .lastName("{{last_name}}")
        .emailAddress("{{email_address}}")
        .phoneNumber("{{phone_number}}")
        .metaData(Map.of("customer_id", "{{customer_id}}")));
```

</CodeGroup>

    <TutorialFrame>
      <Tabs>
        <Tab title="Try it out">
          <TutorialForm id="create-buyer" method="POST" path="/identities" save="alice_identity_id" response="identity_id">
            <TutorialValue name="identity_type" value="individual" />
            <TutorialInput name="first_name" label="First name" value="Alice" />
            <TutorialInput name="last_name" label="Last name" value="Johnson" />
            <TutorialInput name="email_address" label="Email" value="alice@example.com" />
            <TutorialInput name="phone_number" label="Phone" value="+1234567890" />
            <TutorialInput name="customer_id" label="Customer id" value="CUST-001" group="meta_data" />
          </TutorialForm>
        </Tab>
        <Tab title="Response">
          <TutorialResponse />
        </Tab>
      </Tabs>
    </TutorialFrame>
  </TutorialStep>

  <TutorialStep title="Create the seller">
    Bob is the seller, so he gets his own identity. We pass his type, name, email, and phone number, and store his customer id as `customer_id`.

    Create the seller:

<CodeGroup>

```bash cURL wrap
curl -X POST "{{CORE_URL}}/identities" \
  -H "X-blnk-key: {{CORE_SECRET_KEY}}" \
  -H "Content-Type: application/json" \
  -d '{
    "identity_type": "individual",
    "first_name": "{{first_name}}",
    "last_name": "{{last_name}}",
    "email_address": "{{email_address}}",
    "phone_number": "{{phone_number}}",
    "meta_data": {
      "customer_id": "{{customer_id}}"
    }
  }'
```

```typescript TypeScript wrap
const response = await blnk.Identity.create({
  identity_type: 'individual',
  first_name: '{{first_name}}',
  last_name: '{{last_name}}',
  email_address: '{{email_address}}',
  phone_number: '{{phone_number}}',
  meta_data: {
    customer_id: '{{customer_id}}',
  },
});
```

```go Go wrap
identity, _, _ := client.Identity.Create(blnkgo.Identity{
    IdentityType: blnkgo.Individual,
    FirstName: "{{first_name}}",
    LastName: "{{last_name}}",
    EmailAddress: "{{email_address}}",
    PhoneNumber: "{{phone_number}}",
    MetaData: blnkgo.MetaData{
        "customer_id": "{{customer_id}}",
    },
})
```

```python Python wrap
response = blnk.identity.create({
  "identity_type": "individual",
  "first_name": "{{first_name}}",
  "last_name": "{{last_name}}",
  "email_address": "{{email_address}}",
  "phone_number": "{{phone_number}}",
  "meta_data": {
    "customer_id": "{{customer_id}}",
  },
})
```

```java Java wrap
ApiResponse<JsonNode> response = blnk.identity().create(
    IdentityData.create()
        .identityType("individual")
        .firstName("{{first_name}}")
        .lastName("{{last_name}}")
        .emailAddress("{{email_address}}")
        .phoneNumber("{{phone_number}}")
        .metaData(Map.of("customer_id", "{{customer_id}}")));
```

</CodeGroup>

    <TutorialFrame>
      <Tabs>
        <Tab title="Try it out">
          <TutorialForm id="create-seller" method="POST" path="/identities" save="bob_identity_id" response="identity_id">
            <TutorialValue name="identity_type" value="individual" />
            <TutorialInput name="first_name" label="First name" value="Bob" />
            <TutorialInput name="last_name" label="Last name" value="Smith" />
            <TutorialInput name="email_address" label="Email" value="bob@example.com" />
            <TutorialInput name="phone_number" label="Phone" value="+1234567891" />
            <TutorialInput name="customer_id" label="Customer id" value="CUST-002" group="meta_data" />
          </TutorialForm>
        </Tab>
        <Tab title="Response">
          <TutorialResponse />
        </Tab>
      </Tabs>
    </TutorialFrame>
  </TutorialStep>

  <TutorialStep title="Create the buyer's wallet">
    Now that we have Alice in our ledger, we can create a balance for her, stored in the ledger from step 2.

    We'll use the `ledger_id` from that step and Alice's `identity_id`, set the currency, and submit. Blnk creates the balance and attaches it to Alice.

    Create the buyer's wallet:

<CodeGroup>

```bash cURL wrap
curl -X POST "{{CORE_URL}}/balances" \
  -H "X-blnk-key: {{CORE_SECRET_KEY}}" \
  -H "Content-Type: application/json" \
  -d '{
    "ledger_id": "{{ledger_id}}",
    "identity_id": "{{identity_id}}",
    "currency": "{{currency}}"
  }'
```

```typescript TypeScript wrap
const response = await blnk.LedgerBalances.create({
  ledger_id: '{{ledger_id}}',
  identity_id: '{{identity_id}}',
  currency: '{{currency}}',
});
```

```go Go wrap
balance, _, _ := client.LedgerBalance.Create(blnkgo.CreateLedgerBalanceRequest{
    LedgerID: "{{ledger_id}}",
    IdentityID: "{{identity_id}}",
    Currency: "{{currency}}",
})
```

```python Python wrap
response = blnk.ledger_balances.create({
  "ledger_id": "{{ledger_id}}",
  "identity_id": "{{identity_id}}",
  "currency": "{{currency}}",
})
```

```java Java wrap
ApiResponse<JsonNode> response = blnk.ledgerBalances().create(
    CreateLedgerBalance.create()
        .ledgerId("{{ledger_id}}")
        .identityId("{{identity_id}}")
        .currency("{{currency}}"));
```

</CodeGroup>

    <TutorialFrame>
      <Tabs>
        <Tab title="Try it out">
          <TutorialForm id="create-buyer-balance" method="POST" path="/balances" save="alice_balance_id" response="balance_id">
            <TutorialInput name="ledger_id" label="Ledger" from="ledger_id" />
            <TutorialInput name="identity_id" label="Identity" from="alice_identity_id" />
            <TutorialInput name="currency" label="Currency" value="USD" />
          </TutorialForm>
        </Tab>
        <Tab title="Response">
          <TutorialResponse />
        </Tab>
      </Tabs>
    </TutorialFrame>
  </TutorialStep>

  <TutorialStep title="Create the seller's wallet">
    Bob needs a balance in the same ledger to receive the payment. We'll use his `identity_id`, set the currency, and submit.

    Create the seller's wallet:

<CodeGroup>

```bash cURL wrap
curl -X POST "{{CORE_URL}}/balances" \
  -H "X-blnk-key: {{CORE_SECRET_KEY}}" \
  -H "Content-Type: application/json" \
  -d '{
    "ledger_id": "{{ledger_id}}",
    "identity_id": "{{identity_id}}",
    "currency": "{{currency}}"
  }'
```

```typescript TypeScript wrap
const response = await blnk.LedgerBalances.create({
  ledger_id: '{{ledger_id}}',
  identity_id: '{{identity_id}}',
  currency: '{{currency}}',
});
```

```go Go wrap
balance, _, _ := client.LedgerBalance.Create(blnkgo.CreateLedgerBalanceRequest{
    LedgerID: "{{ledger_id}}",
    IdentityID: "{{identity_id}}",
    Currency: "{{currency}}",
})
```

```python Python wrap
response = blnk.ledger_balances.create({
  "ledger_id": "{{ledger_id}}",
  "identity_id": "{{identity_id}}",
  "currency": "{{currency}}",
})
```

```java Java wrap
ApiResponse<JsonNode> response = blnk.ledgerBalances().create(
    CreateLedgerBalance.create()
        .ledgerId("{{ledger_id}}")
        .identityId("{{identity_id}}")
        .currency("{{currency}}"));
```

</CodeGroup>

    <TutorialFrame>
      <Tabs>
        <Tab title="Try it out">
          <TutorialForm id="create-seller-balance" method="POST" path="/balances" save="bob_balance_id" response="balance_id">
            <TutorialInput name="ledger_id" label="Ledger" from="ledger_id" />
            <TutorialInput name="identity_id" label="Identity" from="bob_identity_id" />
            <TutorialInput name="currency" label="Currency" value="USD" />
          </TutorialForm>
        </Tab>
        <Tab title="Response">
          <TutorialResponse />
        </Tab>
      </Tabs>
    </TutorialFrame>
  </TutorialStep>

  <TutorialStep title="Record a deposit">
    Alice deposits \$100.00 via an ACH transfer so she can pay Bob. The destination is her balance, and the source is `@WorldUSD_ACH`.

    The amount is a decimal, and we multiply it by [precision](/transactions/precision) `100` to get `precise_amount`. The reference and the description are already set.

    <Note>
      To make sure the transaction applies, we need to turn on [overdraft](/transactions/overdrafts). Without it, `@WorldUSD_ACH` is created with a 0 balance, and Blnk would reject the record because the source has insufficient funds.
    </Note>

    Record the deposit:

<CodeGroup>

```bash cURL wrap
curl -X POST "{{CORE_URL}}/transactions" \
  -H "X-blnk-key: {{CORE_SECRET_KEY}}" \
  -H "Content-Type: application/json" \
  -d '{
    "precise_amount": {{precise_amount}},
    "precision": 100,
    "reference": "DEP-20261019-103000",
    "description": "ACH deposit",
    "currency": "{{currency}}",
    "source": "{{source}}",
    "destination": "{{destination}}",
    "allow_overdraft": true
  }'
```

```typescript TypeScript wrap
const amount = {{amount}};
const precision = 100;
const precise_amount = Math.round(amount * precision);

const response = await blnk.Transactions.create({
  precise_amount,
  precision,
  reference: 'DEP-20261019-103000',
  description: 'ACH deposit',
  currency: '{{currency}}',
  source: '{{source}}',
  destination: '{{destination}}',
  allow_overdraft: true,
});
```

```go Go wrap
amount := {{amount}}
precision := 100
preciseAmount := big.NewInt(int64(math.Round(amount * float64(precision))))

transaction, _, _ := client.Transaction.Create(blnkgo.CreateTransactionRequest{
    ParentTransaction: blnkgo.ParentTransaction{
        PreciseAmount: preciseAmount,
        Precision: precision,
        Reference: "DEP-20261019-103000",
        Description: "ACH deposit",
        Currency: "{{currency}}",
        Source: "{{source}}",
        Destination: "{{destination}}",
    },
    AllowOverdraft: true,
})
```

```python Python wrap
amount = {{amount}}
precision = 100
precise_amount = round(amount * precision)

response = blnk.transactions.create({
  "precise_amount": precise_amount,
  "precision": precision,
  "reference": "DEP-20261019-103000",
  "description": "ACH deposit",
  "currency": "{{currency}}",
  "source": "{{source}}",
  "destination": "{{destination}}",
  "allow_overdraft": True,
})
```

```java Java wrap
double amount = {{amount}};
int precision = 100;
int preciseAmount = (int) Math.round(amount * precision);

ApiResponse<JsonNode> response = blnk.transactions().create(
    CreateTransactions.create()
        .preciseAmount(preciseAmount)
        .precision(precision)
        .reference("DEP-20261019-103000")
        .description("ACH deposit")
        .currency("{{currency}}")
        .source("{{source}}")
        .destination("{{destination}}")
        .allowOverdraft(true));
```

</CodeGroup>

    <TutorialFrame>
      <Tabs>
        <Tab title="Try it out">
          <TutorialForm id="record-deposit" method="POST" path="/transactions">
            <TutorialValue name="reference" value="DEP-20261019-103000" />
            <TutorialValue name="description" value="ACH deposit" />
            <TutorialValue name="allow_overdraft" value="true" type="boolean" />
            <TutorialInput name="amount" label="Amount" value="100.00" type="amount" precision="100" />
            <TutorialInput name="currency" label="Currency" value="USD" />
            <TutorialInput name="source" label="Source" value="@WorldUSD_ACH" />
            <TutorialInput name="destination" label="Destination" from="alice_balance_id" />
          </TutorialForm>
        </Tab>
        <Tab title="Response">
          <TutorialResponse />
        </Tab>
      </Tabs>
    </TutorialFrame>
  </TutorialStep>

  <TutorialStep title="Hold the payment">
    Alice pays Bob \$100.00, and we hold it until the sale is done. The source is her balance, the destination is his, and `inflight` is on so neither balance changes yet.

    The reference and the description are already set. Blnk records the pending amount on the inflight fields and leaves the main balances where they are.

    Hold the payment:

<CodeGroup>

```bash cURL wrap
curl -X POST "{{CORE_URL}}/transactions" \
  -H "X-blnk-key: {{CORE_SECRET_KEY}}" \
  -H "Content-Type: application/json" \
  -d '{
    "precise_amount": {{precise_amount}},
    "precision": 100,
    "reference": "ESC-20261019-103000",
    "description": "Escrow payment",
    "currency": "{{currency}}",
    "source": "{{source}}",
    "destination": "{{destination}}",
    "inflight": true
  }'
```

```typescript TypeScript wrap
const amount = {{amount}};
const precision = 100;
const precise_amount = Math.round(amount * precision);

const response = await blnk.Transactions.create({
  precise_amount,
  precision,
  reference: 'ESC-20261019-103000',
  description: 'Escrow payment',
  currency: '{{currency}}',
  source: '{{source}}',
  destination: '{{destination}}',
  inflight: true,
});
```

```go Go wrap
amount := {{amount}}
precision := 100
preciseAmount := big.NewInt(int64(math.Round(amount * float64(precision))))

transaction, _, _ := client.Transaction.Create(blnkgo.CreateTransactionRequest{
    ParentTransaction: blnkgo.ParentTransaction{
        PreciseAmount: preciseAmount,
        Precision: precision,
        Reference: "ESC-20261019-103000",
        Description: "Escrow payment",
        Currency: "{{currency}}",
        Source: "{{source}}",
        Destination: "{{destination}}",
    },
    Inflight: true,
})
```

```python Python wrap
amount = {{amount}}
precision = 100
precise_amount = round(amount * precision)

response = blnk.transactions.create({
  "precise_amount": precise_amount,
  "precision": precision,
  "reference": "ESC-20261019-103000",
  "description": "Escrow payment",
  "currency": "{{currency}}",
  "source": "{{source}}",
  "destination": "{{destination}}",
  "inflight": True,
})
```

```java Java wrap
double amount = {{amount}};
int precision = 100;
int preciseAmount = (int) Math.round(amount * precision);

ApiResponse<JsonNode> response = blnk.transactions().create(
    CreateTransactions.create()
        .preciseAmount(preciseAmount)
        .precision(precision)
        .reference("ESC-20261019-103000")
        .description("Escrow payment")
        .currency("{{currency}}")
        .source("{{source}}")
        .destination("{{destination}}")
        .inflight(true));
```

</CodeGroup>

    <TutorialFrame>
      <Tabs>
        <Tab title="Try it out">
          <TutorialForm id="hold-payment" method="POST" path="/transactions" save="transaction_id">
            <TutorialValue name="reference" value="ESC-20261019-103000" />
            <TutorialValue name="description" value="Escrow payment" />
            <TutorialValue name="inflight" value="true" type="boolean" />
            <TutorialInput name="amount" label="Amount" value="100.00" type="amount" precision="100" />
            <TutorialInput name="currency" label="Currency" value="USD" />
            <TutorialInput name="source" label="Source" from="alice_balance_id" />
            <TutorialInput name="destination" label="Destination" from="bob_balance_id" />
          </TutorialForm>
        </Tab>
        <Tab title="Response">
          <TutorialResponse />
        </Tab>
      </Tabs>
    </TutorialFrame>
  </TutorialStep>

  <TutorialStep title="Commit the hold">
    The sale is done, so we [commit](/transactions/inflight/commit-inflight) the hold. We send `commit` for the transaction from the previous step, and the \$100.00 moves from Alice to Bob.

    <Note>
      Blnk queues the commit. The response stays `INFLIGHT` until the worker applies it, and then Bob's balance shows the \$100.00.
    </Note>

    <Tip>
      If the sale falls through, [void](/transactions/inflight/void-inflight) the hold instead, and Alice keeps the \$100.00. To release part of it, commit a `precise_amount`, and the rest stays inflight.
    </Tip>

    Commit the hold:

<CodeGroup>

```bash cURL wrap
curl -X PUT "{{CORE_URL}}/transactions/inflight/{{transaction_id}}" \
  -H "X-blnk-key: {{CORE_SECRET_KEY}}" \
  -H "Content-Type: application/json" \
  -d '{
    "status": "commit"
  }'
```

```typescript TypeScript wrap
const response = await blnk.Transactions.updateStatus(
  '{{transaction_id}}',
  {
    status: 'commit',
  },
);
```

```go Go wrap
transaction, _, _ := client.Transaction.Update(
    "{{transaction_id}}",
    blnkgo.UpdateStatus{
        Status: blnkgo.InflightStatusCommit,
    },
)
```

```python Python wrap
response = blnk.transactions.update_status(
  "{{transaction_id}}",
  {
    "status": "commit",
  },
)
```

```java Java wrap
ApiResponse<JsonNode> response = blnk.transactions().updateStatus(
    "{{transaction_id}}",
    UpdateTransactionStatus.create()
        .status("commit"));
```

</CodeGroup>

    <TutorialFrame>
      <Tabs>
        <Tab title="Try it out">
          <TutorialForm id="commit-hold" method="PUT" path="/transactions/inflight/{transaction_id}">
            <TutorialValue name="status" value="commit" />
            <TutorialInput name="transaction_id" label="Transaction" from="transaction_id" />
          </TutorialForm>
        </Tab>
        <Tab title="Response">
          <TutorialResponse />
        </Tab>
      </Tabs>
    </TutorialFrame>
  </TutorialStep>

  <TutorialStep title="Confirmation">
    The commit is queued. When it is applied, Bob's balance is \$100.00 and Alice's is 0.

    If you have a Blnk Cloud account or an Enterprise license, you can confirm this by checking the dashboard or using the [CLI](/cli/balances).

    Print every balance:

```bash List balances wrap
blnk balances list
```

  </TutorialStep>
</TutorialSteps>