Non-Custodial Payout Orchestration
This tutorial walks you through how to orchestrate payouts using non-custodial organizations and the non-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 non-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
Overview
This guide will walk you through the full non-custodial payout flow using the NonCustodialSDK
and the Mural API. We will cover:
- Creation of a Non-Custodial Organization
- Using the
NonCustodialSDK
in combination with the existing Mural API to orchestrate and execute a Payout from a Mural Account for your user
1. Create a Non-Custodial Organization for your users
You must first create an Organization for your users. To create an Organization that supports non-custodial execution of Payouts Requests, use the nonCustodialIndividual
or nonCustodialBusiness
type when creating the Organization.
Note
Only 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": "nonCustodialBusiness",
"approvers": [
{
"name": "John Doe",
"email": "[email protected]"
}
],
"businessName": "Sun Tree Capital, LLC",
"email": "[email protected]"
}
'
This non-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 NonCustodialSDK.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/non-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/non-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
Important
Do 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/non-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 9 days ago