movere Docs

Sandbox

Audience: anyone integrating Mooov — platforms on Mooov Connect and merchants using a direct API key. Build and certify with test keys before you touch live money.

Test mode uses the same canonical API host and the same HMAC auth, webhook envelopes, and state machine as live — no real money moves. The API key environment is authoritative.

Building with an AI agent or MCP-capable editor? It can pull these docs and schemas itself; see Connect your AI agent.

1. Base URLs

API         https://api.mooov.money
Authorize   https://connect.mooov.money/authorize     (Connect only)

Use mk_test_* (or a Mooov-Test-Mode platform key) against the canonical host. https://sandbox.api.mooov.money remains a temporary compatibility alias only; it does not select the request environment.

Test and live objects are completely separate: separate keys, separate merchants, separate grants, separate webhook destinations. Nothing you create with a test key exists in live. The full hostname matrix (portals, docs, MCP) is on the environments page.

2. Test credentials

The environment is baked into your API key at minting time. Call https://api.mooov.money with mk_test_* keys while building. Test keys are visually distinct so a leaked-into-live-config mistake is greppable:

Credential Test prefix Live prefix
Merchant API key mk_test_ mk_live_
Platform (Connect) key mk_platform_ (Mooov-Test-Mode key) mk_platform_ (live key)

Signing is identical in both environments — the four-line canonical string and headers from §2 of the Connect protocol.

3. Test cards

Which test cards work depends on how your charge routes. Mooov picks the acquiring route per merchant; you don't choose it per request. Most merchants route to Stripe test mode (§3.1). Merchants approved for Mooov's high-risk acquiring route (you'll know — it involves an eligibility review) route to the high-risk sandbox (§3.2).

3.1 Stripe-routed merchants

The standard Stripe test PANs drive every outcome you need to handle:

Card number Result
4242 4242 4242 4242 Approved immediately
4000 0025 0000 3155 Requires 3DS authentication (challenge flow)
4000 0000 0000 9995 Declined — insufficient_funds
4000 0000 0000 0002 Declined — generic card_declined
4000 0000 0000 0341 Attaches for future use, then fails when charged off-session

Use any future expiry date, any 3-digit CVC, and any postcode. On a hosted checkout (flow: "redirect" / flow: "embedded") you type these into the Stripe-hosted form; for server flow you tokenise them first via a test-mode Setup Intent or Payment Element.

Customer-backed and saved-method flows are temporarily unavailable with test credentials because customer/provider mappings are not yet environment-scoped. customer_ref, POST /v1/charges/saved, POST /v1/subscription_checkouts, and GET /v1/customers/by_ref return test_customer_mappings_unavailable without reading live mappings. Direct tokenized test payments remain supported through POST /v1/payment_intents when customer_ref is omitted.

3.2 High-risk-routed merchants (high-risk sandbox)

Three things are different from Stripe before you reach for a card:

Brand Card number Scenario Notes
JCB 3566 1111 1111 1113 Approved, no 3DS The dedicated non-3DS card — use this first
Mastercard 5555 5555 5555 4444 Usually approves without 3DS Can be pushed into authentication; if it fails, use the JCB above
JCB 3566 0000 0000 0001 3DS frictionless Auto-passes, no challenge UI
JCB 3566 0000 0000 0007 3DS challenge Shows a challenge page
Mastercard 5186 0076 0009 0864 3DS frictionless Amount must be exactly HKD 2.01 (amount: 201)
Mastercard 5506 9001 4010 0305 3DS challenge Amount must be exactly HKD 20.90 (amount: 2090)
Visa 4012 0000 3333 0026 3DS challenge Amount must be exactly HKD 2.09 (amount: 209)

Any future expiry and any 3-digit CVV work for all of the above. If the hosted form asks for billing address fields, any plausible values are accepted.

Refunds work the same as Stripe-routed merchants — full and partial via POST /v1/payment_intents/{id}/refund — with one provider rule that also applies in production: the refund window is six months from capture, and the refund currency must match the charge currency.

4. Test tenants (Connect)

Test merchants seed automatically when you exchange an auth code with a Mooov-Test-Mode platform key: run the consent flow from §3 of the Connect protocol against connect.mooov.money/authorize and the resulting merch_test_* merchant is ready to charge immediately. A fresh CI run can spin up tenants without any manual seeding on Mooov's side.

If a test tenant has not finished simulated PSP onboarding you get the same typed 422 merchant_not_charge_capable you'd see in live — see the error reference — so you can build your onboarding-incomplete UX against test mode too.

5. Webhooks in test mode

Register a test webhook destination separately from live (platforms: part of the credential ceremony; merchants: portal → Developers → Webhooks in Test mode). Deliveries are signed with your test webhook secret using the same t=<unix>,v1=<hex> scheme documented in §6 of the Connect protocol.

Two practical ways to trigger events:

  1. Drive a real test payment. Complete a 4242… checkout and you'll receive payment.succeeded within a couple of seconds; refund it via POST /v1/payment_intents/{id}/refund to receive payment.refunded. This exercises the full pipeline end to end and is what we recommend for CI.
  2. Decline paths. Use the decline PANs above to receive payment.failed with a populated failure_code / failure_category.

The full event list and payload shapes are in the webhook event catalog.

Webhook retries behave exactly as in production (exponential backoff: 1s, 5s, 30s, 5m, 30m, 2h, 12h), so you can verify your dedupe logic by returning a 500 from your test endpoint and watching the redeliveries arrive with the same X-Mooov-Delivery semantics.

6. Go-live checklist

Before minting live credentials, confirm with test keys:

Then follow §9 of the Connect protocol (platforms) or mint a live key from the portal (merchants). Keep the base URL as https://api.mooov.money; only the key (and webhook secret) changes.