Sweep Funds to a Treasury Account

Automatically move funds from your end users' Accounts into one treasury Account as soon as they arrive.

The context

Your end users' funds arrive in their own Accounts, but you may want to manage them from one place. A sweep does this. As soon as funds arrive in an end user's Account, your platform moves them into a treasury Account that you control. The treasury Account is usually one of your Parent Organization's Accounts.

Every sweep follows the same pattern, whatever the currency:

  1. Create a Counterparty and a Payout Method for the treasury Account, under each end user's Organization.
  2. Subscribe to a Webhook that tells you when a Payin completes in the end user's Account.
  3. Create and execute a Payout Request that sends the Payin's amount to that Payout Method.

You can build this pattern for any fiat currency or stablecoin that Mural supports.

This guide

In this guide, you build one specific workflow: sweeping USD. Each time a USD payin completes in an end user's Account, you pay its amount out to your Parent Organization's USD Account. The Payout Request sends to a Counterparty and Payout Method that hold the treasury Account's USD details.

For this guide, both Accounts must hold USD as fiat. The end user's Account sends from its USD balance, and the treasury Account receives into its USD balance. Set the holdInFiat settlement strategy on both Accounts before you start.

The examples use these shell variables:

export MURAL_API_KEY="..."
export MURAL_TRANSFER_API_KEY="..."

Set up the sweep

1. Get the treasury Account's USD details

Use the get Accounts endpoint to list your Accounts. Leave off the on-behalf-of header to get your Parent Organization's Accounts.

curl --request GET \
     --url https://api.muralpay.com/api/accounts \
     --header "accept: application/json" \
     --header "authorization: Bearer $MURAL_API_KEY"

On the treasury Account, find the USD payin method in accountDetails.payinMethods. Note its bankName, bankRoutingNumber, and bankAccountNumber in payinRailDetails, and the Account's id. You use them in the next steps.

export TREASURY_ACCOUNT_ID="<the treasury Account's id>"

2. Create a Counterparty and Payout Method for each end user

Complete this step once for each end user's Organization, and store the counterparty ID and payout method ID you get for it.

First, use the create Counterparty endpoint with the on-behalf-of header, so the counterparty belongs to the end user's Organization. Use your Parent Organization's legal name and address.

export ORGANIZATION_ID="<the end user's Organization ID>"

curl --request POST \
     --url https://api.muralpay.com/api/counterparties \
     --header "accept: application/json" \
     --header "authorization: Bearer $MURAL_API_KEY" \
     --header "content-type: application/json" \
     --header "on-behalf-of: $ORGANIZATION_ID" \
     --data '
{
  "counterparty": {
    "type": "business",
    "name": "Your Company LLC",
    "email": "[email protected]",
    "physicalAddress": {
      "address1": "123 Main St.",
      "city": "Austin",
      "subDivision": "TX",
      "postalCode": "78730",
      "country": "US"
    }
  }
}
'
export COUNTERPARTY_ID="<id from the response>"

Then use the create Payout Method endpoint to add the treasury Account's USD details from step 1 to the counterparty. Set transferType to WIRE.

curl --request POST \
     --url https://api.muralpay.com/api/counterparties/$COUNTERPARTY_ID/payout-methods \
     --header "accept: application/json" \
     --header "authorization: Bearer $MURAL_API_KEY" \
     --header "content-type: application/json" \
     --header "on-behalf-of: $ORGANIZATION_ID" \
     --data '
{
  "alias": "Treasury Account",
  "payoutMethod": {
    "type": "usd",
    "details": {
      "type": "usdDomestic",
      "symbol": "USD",
      "bankName": "<bankName>",
      "bankRoutingNumber": "<bankRoutingNumber>",
      "bankAccountNumber": "<bankAccountNumber>",
      "accountType": "CHECKING",
      "transferType": "WIRE"
    }
  }
}
'

Store the counterparty ID and the payout method id from the response with the end user's Organization ID. Every sweep from that Organization uses them.

3. Subscribe to payin and payout events

Use the create Webhook endpoint to subscribe to the PAYIN and PAYOUT_REQUEST categories, then enable the webhook. See Webhooks for setup and signature validation.

Webhooks belong to your Parent Organization, so the endpoint does not take an on-behalf-of header. One subscription receives events for your Parent Organization and for every end user's Organization. Each event's organizationId tells you which Organization it is for.

curl --request POST \
     --url https://api.muralpay.com/api/webhooks \
     --header "accept: application/json" \
     --header "authorization: Bearer $MURAL_API_KEY" \
     --header "content-type: application/json" \
     --data '
{
  "url": "https://yourcompany.com/webhooks/mural",
  "categories": ["PAYIN", "PAYOUT_REQUEST"]
}
'

Run a sweep

Your webhook handler runs these steps each time a payin completes.

4. Receive the payin webhook

Mural sends a payin_status_changed event each time a payin changes status. Sweep only when currentStatus.type is completed:

{
  "eventCategory": "PAYIN",
  "payload": {
    "type": "payin_status_changed",
    "organizationId": "e77ee023-fa9a-4d42-9be9-74f3a2bb2042",
    "payinId": "d192b782-647c-4011-8184-ab9c1e1b5b3d",
    "statusChangeDetails": {
      "previousStatus": { "type": "pending" },
      "currentStatus": { "type": "completed" }
    }
  }
}

Reminder: the treasury Account gets Payins too. Each sweep arrives in the treasury Account as a Payin, which sends its own payin_status_changed event. Check the event's organizationId. If it is your Parent Organization's ID, the Payin is a sweep arriving, so do not sweep it.

5. Get the payin

Use the get Payin endpoint with the on-behalf-of header set to the event's organizationId.

export ORGANIZATION_ID="<organizationId from the event>"
export PAYIN_ID="<payinId from the event>"

curl --request GET \
     --url https://api.muralpay.com/api/payins/payin/$PAYIN_ID \
     --header "accept: application/json" \
     --header "authorization: Bearer $MURAL_API_KEY" \
     --header "on-behalf-of: $ORGANIZATION_ID"

From the response, use:

  • destinationAccountId: the Account to sweep from.
  • settlementDetails.amount.value: the USD amount that landed in the Account, in cents. This is the amount to sweep.
export ACCOUNT_ID="<destinationAccountId>"
export SWEEP_AMOUNT="<settlementDetails.amount.value>"
export COUNTERPARTY_ID="<the counterparty ID you stored for this Organization>"
export PAYOUT_METHOD_ID="<the payout method ID you stored for this Organization>"

6. Create the sweep payout

Use the create Payout Request endpoint with the on-behalf-of header. Debit the end user's USD balance with amountV2, and send to the counterparty and payout method you stored in step 2.

Set the idempotency-key header to the payin ID. Mural can deliver the same webhook more than once, and the idempotency key makes sure each payin is swept only once.

curl --request POST \
     --url https://api.muralpay.com/api/payouts/payout \
     --header "accept: application/json" \
     --header "authorization: Bearer $MURAL_API_KEY" \
     --header "content-type: application/json" \
     --header "on-behalf-of: $ORGANIZATION_ID" \
     --header "idempotency-key: $PAYIN_ID" \
     --data @- <<EOF
{
  "sourceAccountId": "$ACCOUNT_ID",
  "memo": "Treasury sweep",
  "payouts": [
    {
      "amountV2": {
        "type": "sourceAmount",
        "sourceCurrency": { "type": "fiat", "fiatCurrencyCode": "USD" },
        "value": "$SWEEP_AMOUNT"
      },
      "recipientInfo": {
        "type": "counterpartyInfo",
        "counterpartyId": "$COUNTERPARTY_ID"
      },
      "payoutDetails": {
        "type": "counterpartyPayoutMethod",
        "payoutMethodId": "$PAYOUT_METHOD_ID"
      }
    }
  ]
}
EOF
export PAYOUT_REQUEST_ID="<id from the response>"

7. Execute the sweep

Use the execute Payout Request endpoint with the transfer-api-key header.

curl --request POST \
     --url https://api.muralpay.com/api/payouts/payout/$PAYOUT_REQUEST_ID/execute \
     --header "accept: application/json" \
     --header "authorization: Bearer $MURAL_API_KEY" \
     --header "on-behalf-of: $ORGANIZATION_ID" \
     --header "transfer-api-key: $MURAL_TRANSFER_API_KEY"

8. Confirm the sweep

Mural sends a payout_status_changed event as the payout moves from pending to completed. The funds then arrive in the treasury Account as a payin, and its USD balance in accountDetails.balancesV2 grows by the swept amount.

Fees

A sweep has no flat fee and no platform fee. You cannot add a developer fee to a sweep.


Did this page help you?