End-User Custodial Payout Orchestration
This tutorial walks you through how to orchestrate payouts using end-user custodial organizations and the end-user custodial payout flow. For more information on Mural's custody models, check out the Custody Models documentation.
Prerequisites
Before going through this tutorial, you should have general understanding of how to interface with the Mural API (see integration guide here)
To enable end-user custodial payments, we provide an SDK that can be integrated into your app, allowing users to sign transactions securely and in a non-custodial way.
It can be installed via npm:
npm install @muralpay/browser-sdk
You can also follow this Demo we built, which covers the full flow from account creation to payment execution and can be used as a reference.
Overview
This guide will walk you through the full end-user custodial payout flow using the EndUserCustodialSDK
and the Mural API. We will cover:
- Creation of a End-User Custodial Organization
- Using the
EndUserCustodialSDK
in combination with the existing Mural API to orchestrate and execute a Payout from a Mural Account for your user
1. Create an End-User Custodial Organization for your users
You must first create an Organization for your users. To create an Organization that supports end-user custodial execution of Payouts Requests, use the endUserCustodialIndividual
or endUserCustodialBusiness
type when creating the Organization.
NoteOnly Approvers provided during the Organization creation process are able to approve Payout Requests
curl --request POST \
--url https://api.muralpay.com/api/organizations \
--header 'accept: application/json' \
--header 'authorization: Bearer $API_KEY' \
--header 'content-type: application/json' \
--data '
{
"type": "endUserCustodialBusiness",
"approvers": [
{
"name": "John Doe",
"email": "[email protected]"
}
],
"businessName": "Sun Tree Capital, LLC",
"email": "[email protected]"
}
'
This end-user custodial org that is being created will be used for the on-behalf-of
of the next steps.
2. Create the SDK Instance
Mount an empty iframe in your app. Note, this iFrame will not be visible to your user. Initialize the SDK by calling createInstance
.
Configuration
Field | Type | Description |
---|---|---|
iframeContainerId | string | The ID of the HTML element where the iframe will be mounted |
iframeElement | HTMLElement | The actual HTML element (must match the ID above) |
HTML:
<div id="mural-auth-iframe-container-id"></div>
JavaScript:
const sdk = await EndUserCustodialSDK.createInstance({
iframeContainerId: 'mural-auth-iframe-container-id',
iframeElement: document.getElementById('mural-auth-iframe-container-id')
});
3. Trigger Authentication Challenge
Trigger an authentication challenge for the user by calling the Initiate Challenge endpoint. Note, this will send an email with a special code to the Approver.
curl --request POST \
--url https://api.muralpay.com/api/approvers/end-user-custodial/initiate-challenge \
--header 'accept: application/json' \
--header 'on-behalf-of: $ORG_ID' \
--header 'authorization: Bearer $API_KEY' \
--header 'content-type: application/json' \
--data '
{
"publicKey": "04a82bfb0edb13d...",
"approverId": "user123"
}
'
Payload:
publicKey
β Usesdk.getPublicKey()
approverId
β The approvers's ID
const publicKey = sdk.getPublicKey();
Response: Contains authenticatorId
for use in next step.
4. Start Session with Verification Code
Once the user receives their email verification code, collect it and call startSession
:
await sdk.startSession({
code: $code-from-user-email
authenticatorId: $authenticator-id-from-initiate-challenge
});
This creates a secure, authenticated session. You donβt need to call it again for each signing operation.
The SDK provides two helper functions, sdk.isSessionActive()
and sdk.getClientSessionExpiry()
, to check the session status and expiration.
5. Sign the Payout
Retrieve the payload to sign by calling the Get a Payout Request Body endpoint:
curl --request GET \
--url https://api.muralpay.com/api/payouts/payout/end-user-custodial/body-to-sign/:id \
--header 'accept: application/json' \
--header 'on-behalf-of: $ORG_ID' \
--header 'authorization: Bearer $API_KEY'
:id
is the payout request ID
Then sign it using the SDK:
const signature = await sdk.signPayoutPayload(body); // use the exact body as returned
ImportantDo not alter the body returned by the API. Modifications will invalidate the signature.
5. Execute the Payout Request
Send the signature to the Execute the Payout endpoint.
curl --request POST \
--url https://api.muralpay.com/api/payouts/payout/end-user-custodial/execute/:id \
--header 'accept: application/json' \
--header 'content-type: application/json' \
--header 'on-behalf-of: $ORG_ID' \
--header 'authorization: Bearer $API_KEY' \
--data '
{
"signature": "<signature-from-sdk>"
}
'
Updated 28 days ago