Create a Merchant
Create a merchant wallet with a complete business profile — name, location, tax regime, settlement account — so every payment artifact the merchant creates inherits the right data automatically.
Adding a new merchant to a traditional payment system means filling out forms for every payment network the merchant needs to accept. QR codes require one set of fields, alias directories require another, card networks require a third. Each network has its own onboarding format, its own validation rules, and its own way of representing the same merchant.
With Minka, you create a single wallet that describes the merchant as a business entity — name, city, category, tax regime, settlement account. Every payment artifact the merchant creates afterward — QR codes, payment links, aliases — inherits this profile automatically. The bridge reads the wallet and translates it into whatever format the network requires. One onboarding, any network.
What you're building
In this guide you'll create a merchant wallet hierarchy — a parent merchant wallet that holds the full business profile and a store wallet that inherits from it. You'll also create a settlement account where the merchant's money lands. Once the merchant wallet exists, the merchant can create QR codes and other payment instruments without repeating their business details.
This guide uses a merchant as the example, but the same wallet creation pattern applies to any participant type. A bank creates an institution wallet with its own schema and regulatory fields. An individual creates a person wallet with identity details. The API call is identical — only the schema and custom fields change.
Prerequisites
- SDK credentials and a connection to a ledger
- A ledger with the
copsymbol (or your currency) already created - The
merchant-walletandsettlement-walletschemas deployed on the ledger
Create the merchant wallet
Create the settlement account
The settlement account is where the merchant's money actually lands. It represents the merchant's real bank account on the ledger — a savings account, checking account, or deposit account at the hub bank. Settlement wallets are linked to the ACH bridge so that any intent involving them triggers two-phase commit with the external network.
minka wallet createThe CLI walks you through each field:
| Prompt | What to enter |
|---|---|
| Handle | The account address, e.g. svgs:4242@bank.co |
| Schema | settlement-wallet |
| Bridge | bridge@achcolombia.com.co |
| Custom | {"name":"Starbucks Settlement","type":"settlement","bankCode":"007","accountType":"savings"} |
curl -X POST "https://{ledger-host}/v2/wallets" \
-H "Content-Type: application/json" \
-H "Authorization: Bearer {your-token}" \
-H "x-ledger: {your-ledger}" \
-d '{
"data": {
"handle": "svgs:4242@bank.co",
"schema": "settlement-wallet",
"bridge": "bridge@achcolombia.com.co",
"custom": {
"name": "Starbucks Settlement Account",
"type": "settlement",
"bankCode": "007",
"accountType": "savings"
}
}
}'// SDK setup: see How to Connect to a Ledger
// /moving-money/connect-to-ledger
const { response } = await sdk.wallet
.init()
.data({
handle: 'svgs:4242@bank.co',
schema: 'settlement-wallet',
bridge: 'bridge@achcolombia.com.co',
custom: {
name: 'Starbucks Settlement Account',
type: 'settlement',
bankCode: '007',
accountType: 'savings',
},
})
.hash()
.sign([{ keyPair }])
.send()import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
HttpClient client = HttpClient.newHttpClient();
String json = """
{
"data": {
"handle": "svgs:4242@bank.co",
"schema": "settlement-wallet",
"bridge": "bridge@achcolombia.com.co",
"custom": {
"name": "Starbucks Settlement Account",
"type": "settlement",
"bankCode": "007",
"accountType": "savings"
}
}
}
""";
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create("https://{ledger-host}/v2/wallets"))
.header("Content-Type", "application/json")
.header("Authorization", "Bearer {your-token}")
.header("x-ledger", "{your-ledger}")
.POST(HttpRequest.BodyPublishers.ofString(json))
.build();
HttpResponse<String> response = client.send(
request, HttpResponse.BodyHandlers.ofString());using System.Net.Http;
using System.Text;
using var client = new HttpClient();
client.DefaultRequestHeaders.Add("Authorization", "Bearer {your-token}");
client.DefaultRequestHeaders.Add("x-ledger", "{your-ledger}");
var json = """
{
"data": {
"handle": "svgs:4242@bank.co",
"schema": "settlement-wallet",
"bridge": "bridge@achcolombia.com.co",
"custom": {
"name": "Starbucks Settlement Account",
"type": "settlement",
"bankCode": "007",
"accountType": "savings"
}
}
}
""";
var content = new StringContent(json, Encoding.UTF8, "application/json");
var response = await client.PostAsync(
"https://{ledger-host}/v2/wallets", content);
var body = await response.Content.ReadAsStringAsync();The curl examples use a Bearer token for authentication. In production, every mutation also requires a cryptographic signature — see Sign Payments for the full signing flow. The TypeScript SDK handles signing automatically.
The handle follows the standard addressing format: svgs for savings, 4242 for the account number, bank.co for the institution. The bridge field links this wallet to the ACH bridge — any intent debiting or crediting this wallet triggers two-phase commit with the external network, guaranteeing the ledger and the real bank account stay in sync.
Create the merchant wallet
The merchant wallet is the business identity. Its custom field holds the profile that bridges read when generating payment artifacts — QR payloads, alias registrations, network credentials. This is the parent wallet — it carries the full business profile that store wallets inherit from.
minka wallet create| Prompt | What to enter |
|---|---|
| Handle | The merchant identity, e.g. main@starbucks.com |
| Schema | merchant-wallet |
| Custom | See the profile fields below |
curl -X POST "https://{ledger-host}/v2/wallets" \
-H "Content-Type: application/json" \
-H "Authorization: Bearer {your-token}" \
-H "x-ledger: {your-ledger}" \
-d '{
"data": {
"handle": "main@starbucks.com",
"schema": "merchant-wallet",
"custom": {
"name": "STARBUCKS COLOMBIA",
"city": "BOGOTA",
"taxId": "900123456-7",
"category": "Fast food restaurant",
"country": "CO",
"postal": "110111",
"taxRegime": "standard",
"acquirer": "RBM",
"settlement": "svgs:4242@bank.co",
"type": "merchant"
}
}
}'// SDK setup: see How to Connect to a Ledger
// /moving-money/connect-to-ledger
const { response } = await sdk.wallet
.init()
.data({
handle: 'main@starbucks.com',
schema: 'merchant-wallet',
custom: {
name: 'STARBUCKS COLOMBIA',
city: 'BOGOTA',
taxId: '900123456-7',
category: 'Fast food restaurant',
country: 'CO',
postal: '110111',
taxRegime: 'standard',
acquirer: 'RBM',
settlement: 'svgs:4242@bank.co',
type: 'merchant',
},
})
.hash()
.sign([{ keyPair }])
.send()import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
HttpClient client = HttpClient.newHttpClient();
String json = """
{
"data": {
"handle": "main@starbucks.com",
"schema": "merchant-wallet",
"custom": {
"name": "STARBUCKS COLOMBIA",
"city": "BOGOTA",
"taxId": "900123456-7",
"category": "Fast food restaurant",
"country": "CO",
"postal": "110111",
"taxRegime": "standard",
"acquirer": "RBM",
"settlement": "svgs:4242@bank.co",
"type": "merchant"
}
}
}
""";
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create("https://{ledger-host}/v2/wallets"))
.header("Content-Type", "application/json")
.header("Authorization", "Bearer {your-token}")
.header("x-ledger", "{your-ledger}")
.POST(HttpRequest.BodyPublishers.ofString(json))
.build();
HttpResponse<String> response = client.send(
request, HttpResponse.BodyHandlers.ofString());using System.Net.Http;
using System.Text;
using var client = new HttpClient();
client.DefaultRequestHeaders.Add("Authorization", "Bearer {your-token}");
client.DefaultRequestHeaders.Add("x-ledger", "{your-ledger}");
var json = """
{
"data": {
"handle": "main@starbucks.com",
"schema": "merchant-wallet",
"custom": {
"name": "STARBUCKS COLOMBIA",
"city": "BOGOTA",
"taxId": "900123456-7",
"category": "Fast food restaurant",
"country": "CO",
"postal": "110111",
"taxRegime": "standard",
"acquirer": "RBM",
"settlement": "svgs:4242@bank.co",
"type": "merchant"
}
}
}
""";
var content = new StringContent(json, Encoding.UTF8, "application/json");
var response = await client.PostAsync(
"https://{ledger-host}/v2/wallets", content);
var body = await response.Content.ReadAsStringAsync();Each field in the profile serves a specific purpose:
| Field | What it does |
|---|---|
| name | The trading name shown to the person paying — appears on their bank app when they scan the QR. Maximum 25 characters. |
| city | The city where this merchant operates. Used for display and geographic routing. Maximum 15 characters. |
| taxId | The merchant's tax identification number — NIT in Colombia, CNPJ in Brazil, EIN in the US. The bridge derives the alias type automatically from this value (a 9-10 digit number with optional check digit is detected as NIT in Colombia). |
| category | A human-readable description of the business ("coffee shop", "grocery store"). The bridge maps this to the correct 4-digit MCC using the EMVco profile's category table — the merchant never needs to look up MCC codes. |
| country | ISO 3166-1 alpha-2 country code. Determines which EMVco profile the bridge uses — different countries have different acquirer GUIDs, currency codes, tax structures, and alias types. |
| postal | Postal or ZIP code of the merchant's location. Some networks require it for geographic validation. |
| taxRegime | How the merchant is taxed: standard (full rates), exempt (no taxes), simplified (flat rate), or special (country-specific rules). The bridge uses this to compute tax tags in the QR payload automatically — the merchant never manually provides tax breakdowns. |
| acquirer | Identifier of the acquiring institution on the payment network. Defaults from the EMVco profile if omitted. |
| settlement | The handle of the wallet where this merchant receives their money. When a QR payment arrives, the bridge reads this field to know where to credit the funds. |
| type | Set to merchant for the parent entity. Store wallets use store, kiosk, or online. |
The merchant sets these fields once during onboarding. When they later create a QR code, the bridge reads this profile, maps the category to an MCC, detects the alias type from the taxId, selects the correct EMVco country profile, computes tax tags from the regime — and generates the complete network payload. The merchant never needs to know what EMVco Tag 52 means or how IVA condition codes work.
Create the store wallet
Store wallets carry only location-specific fields — name, city, postal code. Everything else (taxId, category, country, taxRegime, acquirer, settlement) is inherited from the parent merchant wallet. The bridge resolves inherited fields by reading main@{domain} when a store wallet lacks them — see domain for how domain-level inheritance works.
minka wallet create| Prompt | What to enter |
|---|---|
| Handle | The store address, e.g. store1@starbucks.com |
| Schema | merchant-wallet |
| Custom | {"name":"STARBUCKS UNICENTRO","city":"BOGOTA","postal":"110111","type":"store"} |
curl -X POST "https://{ledger-host}/v2/wallets" \
-H "Content-Type: application/json" \
-H "Authorization: Bearer {your-token}" \
-H "x-ledger: {your-ledger}" \
-d '{
"data": {
"handle": "store1@starbucks.com",
"schema": "merchant-wallet",
"custom": {
"name": "STARBUCKS UNICENTRO",
"city": "BOGOTA",
"postal": "110111",
"type": "store"
}
}
}'// SDK setup: see How to Connect to a Ledger
// /moving-money/connect-to-ledger
const { response } = await sdk.wallet
.init()
.data({
handle: 'store1@starbucks.com',
schema: 'merchant-wallet',
custom: {
name: 'STARBUCKS UNICENTRO',
city: 'BOGOTA',
postal: '110111',
type: 'store',
},
})
.hash()
.sign([{ keyPair }])
.send()import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
HttpClient client = HttpClient.newHttpClient();
String json = """
{
"data": {
"handle": "store1@starbucks.com",
"schema": "merchant-wallet",
"custom": {
"name": "STARBUCKS UNICENTRO",
"city": "BOGOTA",
"postal": "110111",
"type": "store"
}
}
}
""";
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create("https://{ledger-host}/v2/wallets"))
.header("Content-Type", "application/json")
.header("Authorization", "Bearer {your-token}")
.header("x-ledger", "{your-ledger}")
.POST(HttpRequest.BodyPublishers.ofString(json))
.build();
HttpResponse<String> response = client.send(
request, HttpResponse.BodyHandlers.ofString());using System.Net.Http;
using System.Text;
using var client = new HttpClient();
client.DefaultRequestHeaders.Add("Authorization", "Bearer {your-token}");
client.DefaultRequestHeaders.Add("x-ledger", "{your-ledger}");
var json = """
{
"data": {
"handle": "store1@starbucks.com",
"schema": "merchant-wallet",
"custom": {
"name": "STARBUCKS UNICENTRO",
"city": "BOGOTA",
"postal": "110111",
"type": "store"
}
}
}
""";
var content = new StringContent(json, Encoding.UTF8, "application/json");
var response = await client.PostAsync(
"https://{ledger-host}/v2/wallets", content);
var body = await response.Content.ReadAsStringAsync();Four fields instead of twelve. The store wallet is lean because the bridge does the heavy lifting — when it needs the taxId, acquirer, or settlement account, it reads the merchant wallet at main@starbucks.com in the same domain. Changing the acquirer or settlement account means updating one merchant wallet, not every store.
Verify the merchant profile
Confirm the merchant wallet was created with the correct profile:
minka wallet read main@starbucks.comcurl "https://{ledger-host}/v2/wallets/main@starbucks.com" \
-H "Authorization: Bearer {your-token}" \
-H "x-ledger: {your-ledger}"// SDK setup: see How to Connect to a Ledger
// /moving-money/connect-to-ledger
const { wallet } = await sdk.wallet.read('main@starbucks.com')import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
HttpClient client = HttpClient.newHttpClient();
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create("https://{ledger-host}/v2/wallets/main@starbucks.com"))
.header("Authorization", "Bearer {your-token}")
.header("x-ledger", "{your-ledger}")
.GET()
.build();
HttpResponse<String> response = client.send(
request, HttpResponse.BodyHandlers.ofString());using System.Net.Http;
using var client = new HttpClient();
client.DefaultRequestHeaders.Add("Authorization", "Bearer {your-token}");
client.DefaultRequestHeaders.Add("x-ledger", "{your-ledger}");
var response = await client.GetAsync(
"https://{ledger-host}/v2/wallets/main@starbucks.com");
var body = await response.Content.ReadAsStringAsync();The response should include the complete custom object with all profile fields. Check that settlement points to the correct bank account wallet and that category matches the merchant's actual business activity — the bridge will map it to an MCC code.
Adding more stores
A merchant typically has one parent wallet (main@starbucks.com with type: merchant) and one store wallet per physical location (store1@starbucks.com, store2@starbucks.com, each with type: store). All stores share the same settlement account — money from every store flows to the same bank account.
Each store wallet carries only its own name, city, and postal code. Everything else is inherited from the merchant wallet. To add a new store, create a wallet with type: store in the same domain:
minka wallet create
# Handle: store2@starbucks.com
# Schema: merchant-wallet
# Custom: {"name":"STARBUCKS SALITRE","city":"BOGOTA","postal":"110221","type":"store"}How onboarding connects to payment collection
Once the merchant wallet exists, the rest of the payment infrastructure builds on top of it:
QR codes. When you create an anchor pointing to a store wallet, the bridge reads the store's name and city, then walks up to the merchant wallet for the taxId, category, country, and settlement account. It generates the full EMVco payload — MCC resolved from category, alias type detected from taxId, tax tags computed from the regime — in a single API call.
Settlement. When an inbound payment arrives carrying a QR identifier, the bridge resolves the anchor to the store wallet, reads the merchant's settlement field, and creates a credit intent to svgs:4242@bank.co. The merchant's bank account is credited automatically.
New networks. When a new payment network connects to the ledger, existing merchant wallets work with it immediately. The bridge translates the same profile fields — name, city, category, tax regime — into whatever format the new network requires. No re-onboarding, no new forms.
What you learned
- How to create a settlement account wallet linked to an external network bridge
- How to create a merchant wallet with a full business profile
- How to create lean store wallets that inherit from the merchant
- How profile fields like
taxRegimeandcategoryeliminate per-transaction complexity - How the wallet hierarchy enables one-time onboarding across any payment network
Error responses
When a request fails, the ledger returns a structured error with a code, message, and detail:
{
"error": {
"code": "schema-validation-failed",
"message": "Required field 'city' is missing from custom object",
"detail": {
"schema": "merchant-wallet",
"missing": ["city"]
}
}
}Other how-to pages reference this format. The error codes below are the most common for each operation.
Common errors
| Error | Cause | Fix |
|---|---|---|
schema-validation-failed | A required field is missing from the custom object or has the wrong type | Check the schema definition with minka schema read merchant-wallet to see required fields |
handle-already-exists | A wallet with this handle is already registered on the ledger | Use a unique handle, or query the existing wallet with GET /v2/wallets/{handle} |
domain-not-found | The domain in the wallet handle (e.g., starbucks.com) is not registered | Register the domain first, or check for typos in the handle |
Next steps
- Collect with a Static QR — create a QR code anchor that uses this merchant profile
- Collect with a Dynamic QR — generate a per-transaction QR at checkout
- Send a Payment to an Account — push funds to a merchant or individual
- About Wallets — how wallets work as a programmable payment switch
Sign Payments using code
Implement request signing for the Minka ledger API — hash the payload, build the signature digest, and sign with Ed25519. Required when integrating directly without the CLI or SDK.
Send to an Account
Push funds to a specific bank account — payroll, vendor payments, refunds, or disbursements — using a single intent that works across institutions and networks.