Merchant onboarding
How a user becomes a merchant — the registration form, the two independent status fields, the KYC gate, the four keys issued up front, and the exact point at which an API key starts working.
Any signed-in user can apply to become a merchant. One user gets one merchant account — the registration endpoint refuses a second. From your side this is a two-decision review: is this account allowed to trade at all (status), and have you checked who they say they are (verification). They are separate columns, they are set by separate admin actions, and they gate different things.
The two fields, and what each one blocks
status |
verificationStatus |
|
|---|---|---|
| Values | PENDING · ACTIVE · SUSPENDED · REJECTED |
UNVERIFIED · PENDING · VERIFIED |
| Set at registration | PENDING (usually) |
PENDING, always |
| Set by | Merchants → Status | Merchants → Verify |
| Blocks | Every API call. Authentication rejects any key whose merchant is not ACTIVE with a 403. The checkout also refuses to confirm a payment for a non-ACTIVE merchant. |
Issuing new API keys, and editing the merchant's own identity fields. |
Setting a merchant ACTIVE makes their keys work. It does not set
verificationStatus. A merchant left at PENDING verification can take
payments perfectly well but cannot create an additional API key — the endpoint
answers "Merchant account must be verified to create API keys", which reads
like a bug and is a policy. Do both, or expect the support ticket.
What registration does
The merchant fills the form at /gateway/register. Business name and email are
required; website, description, business type, phone and a postal address are
optional.
Behind the form, in order:
- The gateway must be enabled. If
gatewayEnabledis off, registration fails with "Payment gateway is currently disabled". - KYC, if you require it. With
gatewayRequireKycon, the applicant must pass the shared KYC feature gate foruse_gateway. A refusal is a 403 with the reason, not a generic error. - Duplicate check. One merchant per user, by
userId. - A URL slug is generated from the business name and made unique. The form shows a slug field; it is ignored — the model's hook derives it. Same for the tax ID field, which has no column behind it.
- The phone is normalised to
+plus digits only, truncated to 15 characters. Anything a user types that is not a digit is stripped. - Defaults are copied from your platform settings. Fee percentage, fixed fee, payout schedule, payout threshold, daily and monthly limits, and the per-transaction limit are all snapshotted from the gateway settings at the moment of registration. Changing a platform setting later does not move an existing merchant — you edit that merchant's row.
- Wallet types and currencies are derived from
gatewayAllowedWalletTypes: every enabled type, and the first three currencies of each. If your map is empty they fall back toFIAT/USD, which the platform check then rejects. - Four keys are created —
pk_live_,sk_live_,pk_test_,sk_test_— all with the wildcard permission*. testModeis set totrueon the merchant row.
The response is the only time the full key values and the webhook secret are ever shown. They are stored as SHA-256 hashes; the platform genuinely cannot show them again.
Four API keys and the webhook secret come back in one JSON body and are never retrievable. A merchant who closes that screen has to rotate every key and cannot recover the webhook secret at all — it is only re-shown on the merchant dashboard's settings view, and it is masked in demo mode.
Auto-approval needs two switches, not one
gatewayAutoApproveVerified on its own does nothing. A merchant is created
ACTIVE only when both gatewayAutoApproveVerified and
gatewayRequireKyc are on — the logic reads "auto-approve people we made pass
KYC". With KYC off, the auto-approve switch is inert and every merchant lands in
your queue.
Even under auto-approval, verificationStatus is still written as PENDING. So
an auto-approved merchant can transact immediately with the four keys they were
given, and still cannot mint a fifth until a human verifies them.
Editing the profile after registration
Fields split into two groups.
- Always editable: description, logo, test mode, allowed currencies, allowed wallet types, default currency.
- Frozen once
verificationStatusisVERIFIED: name, email, phone, website, business type, address, city, state, country, postal code. A verified merchant who needs one of these changed has to come to you, and you change it from the admin merchant screen.
Note that a webhookUrl, successUrl or cancelUrl sent to this endpoint is
accepted and then dropped — the merchant table has no such columns. Redirect and
webhook destinations are per payment, set in the body of the create-payment
call. See API integration.
API keys
Rules worth knowing before a merchant asks:
- Keys are created in pairs. One request produces both a
pk_and ansk_key in the chosen mode, named<name> (Public)and<name> (Secret). - Ten keys per merchant, total — that is ten rows, so five pairs, and the four created at registration already count. The eleventh request is rejected.
- Only secret keys do anything. Creating a payment, cancelling one and
refunding all require an
sk_. A public key can only call the validate endpoint and read. - Permissions are filtered, not validated. Anything outside
payment.create,payment.read,payment.cancel,refund.create,refund.readand*is silently dropped; if that empties the list it becomes["*"]. A typo therefore grants more access than intended, not less. - The IP allowlist only applies to secret keys, and only when it is
non-empty. It accepts plain IPv4, CIDR ranges and the literal
*. IPv6 entries are compared as strings — the CIDR maths is IPv4-only, so an IPv6 range will not match. - Per-key
successUrl,cancelUrlandwebhookUrlare stored and never read. They round-trip through the API and appear in the UI, but the create-payment call uses the URLs in its own body. Do not tell a merchant to configure a webhook on the key.
The onboarding sequence you should publish to merchants
-
Register at
/gateway/registerand save the four keys and the webhook secret from the confirmation screen. -
Wait for approval. Until an admin sets the account
ACTIVE, every API call returns403 Merchant account is not active. -
Test with the
sk_test_key. Test-mode payments walk the whole flow — wallet selection, exchange rates, webhooks — and move no money. A test key can only see test payments; a live key can only see live ones. Looking up a live payment with a test key returns404, deliberately. -
Point a webhook at your server by including
webhookUrlon each create-payment call, and verify the signature. See Webhooks. -
Switch to the
sk_live_key and place one small real order before you open the till.
Next: API integration.