This is the server-side API walkthrough. If you are building a Flutter app with
bmoni_embedded_sdk, start with the SDK quickstart instead — it covers the client side of the same lifecycle.Before you start
- A sandbox API key, sent as
x-api-keyon every request. - A provisioned wallet from
bmoni_embedded_sdk. Its address becomesuserOwnerAddress, and the SDK signs twice below. - The Bunch Dillon persona from Sandbox test data. Use its details verbatim — verification matches them.
https://embedded.bmoni.com — see Base URL.
1. Create the user
Use the persona’s name and phone, converted to E.164.bmoniUserId from the response. It is the {userId} path parameter for every later call — not your own employee identifier.
2. Submit the KYC profile
3. Prove you control the owner address
Wallet creation requires the owner address to prove control first.challengeId and a message. Sign the message as text, with the EIP-191 prefix — the standard personal_sign your language’s Ethereum library already provides.
4. Create the smart wallet
smartWalletId and the wallet address.
5. Start onboarding
Now that the wallet exists, onboarding can reference it. This is why wallet creation comes first —ngnWalletAddress is required here.
6, 7, 8. Upload the documents
Three separate uploads. All three are required before verification can complete.9. Create the transfer proposal
Nothing moves yet. A proposal records intent.id.
Sending to
toUserId instead of toAddress requires the recipient to already hold an active wallet in that currency. In a fresh sandbox they usually do not, so toAddress is the reliable choice for a first run.10. Approve
status moves to PENDING_SIGNATURES once the threshold is met.
11. Sign and send
Fetch the payload:hashToSign — a raw 32-byte digest, with no prefix. This is the opposite of step 3.
Expected result
GET /v1/users/{userId}/smart-wallets/proposals/{proposalId} reports status progressing PENDING_SIGNATURES → COMPLETED. The balance on the sending wallet drops by 25.
If the signature is rejected, check you used signTransactionHash and not signMessage, then work through Why your signature is rejected.
The two signatures, side by side
Worth keeping in front of you.
Both produce a
0x-prefixed 130-character hex signature. In Go and PHP, remember v comes back as 0/1 and Ethereum expects 27/28 — add 27, as the snippets do.
Related
- Sandbox test data — the personas and the matching rule.
- Sign a proposal — the full signing reference and a reproducible test vector.
- Errors and status codes — every error these calls return.
- Webhooks and events — replacing the polling in step 8.
- Request test tokens — funding the wallet before step 9.
Last reviewed: 7 August 2026.

