Mooov Docs

Merchant quickstart

Audience: a merchant integrating Mooov directly for your own business with an API key from the merchant portal. If you're a platform charging on behalf of many tenants, you want the Connect protocol instead.

Time to first test payment: about fifteen minutes.

1. Mint an API key

In the merchant portal (https://mooov3.mooov.money) go to Developers → API keys and create a key. You'll get:

New keys carry the payments:write and payments:read scopes, which is everything this quickstart needs.

The environment is baked into the key: mk_test_* keys route to PSP test mode, mk_live_* keys move real money. Build against the sandbox first.

2. Sign a request

Mooov uses HMAC request signing — your secret is never sent on the wire. Every request carries:

X-Mooov-Key-Id:    mk_test_xxxxxxxx
X-Mooov-Timestamp: 2026-06-10T12:00:00Z          (RFC3339 UTC, ±5 min skew)
X-Mooov-Signature: <lowercase hex HMAC-SHA256>
Idempotency-Key:   <uuid>                        (required on writes)

The signature is HMAC-SHA256 over exactly four lines (no trailing newline):

<METHOD>\n<PATH>\n<TIMESTAMP>\n<hex SHA-256 of the raw body, or of "" on GET>

A minimal Node signer:

import { createHmac, createHash, randomUUID } from "node:crypto";

const KEY_ID = process.env.MOOOV_KEY_ID!;     // mk_test_…
const SECRET = process.env.MOOOV_SECRET!;
const BASE   = "https://sandbox.api.mooov.money";

export async function mooov(method: string, path: string, payload?: unknown) {
  const body = payload ? JSON.stringify(payload) : "";
  const ts = new Date().toISOString().replace(/\.\d{3}Z$/, "Z");
  const bodyHash = createHash("sha256").update(body).digest("hex");
  const sig = createHmac("sha256", SECRET)
    .update(`${method}\n${path}\n${ts}\n${bodyHash}`)
    .digest("hex");
  return fetch(BASE + path, {
    method,
    headers: {
      "Content-Type":      "application/json",
      "X-Mooov-Key-Id":    KEY_ID,
      "X-Mooov-Timestamp": ts,
      "X-Mooov-Signature": sig,
      ...(method !== "GET" ? { "Idempotency-Key": randomUUID() } : {}),
    },
    body: body || undefined,
  });
}

(A Python equivalent is in §2 of the Connect protocol — drop the Mooov-Merchant header, which is for platform keys only.)

3. Take your first payment

The fastest path is hosted checkout — Mooov opens a checkout session, you redirect the customer to it, and every payment method, SCA challenge, and receipt is handled for you:

const res = await mooov("POST", "/v1/payment_intents", {
  payment_id:  `pay_${crypto.randomUUID()}`,   // your stable id
  amount:      4200,                            // minor units = £42.00
  currency:    "GBP",
  flow:        "redirect",
  success_url: "https://your-site.example/checkout/success",
  cancel_url:  "https://your-site.example/checkout/cancel",
  description: "Order #1042",
});
const { provider } = await res.json();
// → send the customer to provider.hosted_url

In the sandbox, pay with the test card 4242 4242 4242 4242 (any future expiry, any CVC). More test cards — including decline and 3DS paths — are on the sandbox page. If your account is on Mooov's high-risk acquiring route, the test cards (and the sandbox currency, HKD) are different — see §3.2 of the sandbox page.

When the customer completes payment, the payment's state becomes captured and your webhook receives payment.captured.

4. Hear about it (webhooks)

Register a webhook destination in the portal under Developers → Webhooks. You'll receive signed events:

{
  "id": "evt_01HXX…",
  "type": "payment.captured",
  "occurred_at": "2026-06-10T12:34:56Z",
  "merchant_id": "merch_yourbiz",
  "data": {
    "payment_id": "pay_…",
    "state":      "captured",
    "amount":     4200,
    "currency":   "GBP"
  }
}

Verify the X-Mooov-Signature: t=…,v1=… header before trusting the body — code samples and the full event list are in the webhook event catalog. The events you'll handle first:

type Meaning
payment.captured Money captured — fulfil the order
payment.failed Charge failed — data.failure_code says why
payment.refunded / payment.partially_refunded A refund settled

5. Refund a payment

await mooov("POST", `/v1/payment_intents/${paymentId}/refund`, {
  amount: 2100,                     // omit for a full refund
  reason: "customer_request",
});

Partial refunds move the payment to partially_refunded; refunding the rest moves it to refunded. Full request/response shapes — plus capture, void, and the payment timeline — are in the API reference.

6. Go live

  1. Run the sandbox go-live checklist.
  2. Mint an mk_live_* key in the portal.
  3. Swap the base URL to https://api.mooov.money and the key pair in your secret manager. Nothing else changes — the API surface is identical.

Questions: integrations@mooov.money.