Sandbox Quickstart
Sandbox mode is a free test environment baked into MonCashConnect. Build your integration, run end-to-end payment flows, see webhooks fire — all without moving real money or touching real MonCash. When you're ready, swap two secret values and you're live. Same API, same payloads, same response shapes.
Before you start
You need:
- A MonCashConnect account at moncashconnect.com — no identity verification needed to test in Sandbox. KYC is only required to activate Live payments.
- A backend that can make outbound HTTPS requests and host a webhook endpoint.
- A domain where customers will be redirected after payment.
Implement with AI
Most merchants already have an AI coding assistant in their workflow — Lovable, Cursor, Claude, ChatGPT, Copilot. The prompt below encapsulates the entire MCC sandbox integration as a single paste: your assistant will ask you three questions, then write the backend, the webhook, the checkout button and the return page.
Before pasting: have your MCC test secret + your test webhook secret ready (from "Régénérer les clés de test" on the Projects tab of the Developer page) plus the domain of your site.
You are implementing MonCashConnect (MCC) sandbox payments on this project.
MCC is a Stripe-style payment gateway for MonCash (Haiti's mobile money). Sandbox mode lets us test the full payment flow without moving any real money. When we're ready, the same code goes live by swapping two env values — no code change required.
The user will provide three pieces of info. Ask for them before writing code. Do not invent placeholders.
- DOMAIN: the merchant's domain (e.g. mamasoil.shop). It becomes the Origin header on the call to MCC, and must already be in MCC's "Allowed domains" list (the user adds it in the MCC dashboard).
- MCC_SECRET: starts with `sk_test_proj_`. From MCC dashboard → Developer → Projets → "Régénérer les clés de test" modal.
- MCC_WEBHOOK_SECRET: the test webhook secret from the same modal.
== MCC API contract ==
1) Create a payment — your backend → MCC
POST https://hvlmeoqyxaguzcujpmit.supabase.co/functions/v1/pay-create
Headers:
Authorization: Bearer <MCC_SECRET>
Origin: https://<DOMAIN> ← MUST match Allowed domains exactly. Server-side fetch does not set Origin automatically; set it manually.
Content-Type: application/json
Body:
{
"amount": <integer HTG, 1 to 1_000_000>,
"referenceId": "<unique id you generate, UUID is fine>",
"returnUrl": "https://<DOMAIN>/checkout/return",
"customerEmail": "optional@example.com", ← OMIT (not empty string) if absent
"customerName": "optional" ← OMIT if absent
}
Response 200:
{ paymentUrl, reference, expiresAt, livemode: false }
Redirect the browser to paymentUrl. In sandbox it's the MCC simulator (3-button page); in live it's the real MonCash URL.
2) Receive webhook — MCC → your backend
POST <your webhook endpoint, the URL the user will set in MCC project settings>
Headers:
X-MCC-Signature: sha256=<hex>
X-MCC-Timestamp: <unix seconds>
Content-Type: application/json
Body:
{
event: "payment.completed" | "payment.failed" | "payment.cancelled",
reference: "<the referenceId you sent earlier>",
amount: <integer HTG>,
status: "completed" | "failed" | "cancelled",
completedAt: <ISO8601 or null>,
livemode: false ← present on sandbox only; ABSENT on live (do NOT gate with === true)
}
Verification (CRITICAL):
a. Read the body as RAW BYTES first (e.g. req.text() in Deno, raw req in Express). Do NOT parse JSON before verifying — re-serializing reorders keys and breaks HMAC.
b. Compute expected = "sha256=" + hex(HMAC-SHA256(MCC_WEBHOOK_SECRET, rawBody))
c. Compare to X-MCC-Signature header. Reject with 401 if mismatch.
d. Only AFTER verifying, JSON.parse(rawBody) and dispatch on event.
Handle ALL three event types. Returning non-200 on any of them causes MCC to retry forever.
Return 200 with any short body on success.
3) Poll status (webhook fallback) — your return page → MCC, no auth
GET https://hvlmeoqyxaguzcujpmit.supabase.co/functions/v1/pay-status?reference=<referenceId>
Response: { reference, status, amount, ... }
Use this from the customer's /checkout/return page, polling every 3 seconds for up to 90 seconds, in case the webhook is delayed.
== Files to create ==
1. Backend endpoint `create-mcc-payment` (or your stack's equivalent)
Calls MCC pay-create with MCC_SECRET. Must handle CORS preflight (OPTIONS → 204 with CORS headers).
2. Backend endpoint `mcc-webhook`
Receives, verifies HMAC, dispatches on event. Uses raw body for HMAC, parses JSON only after verifying.
3. Frontend "Pay with MonCash" button on the checkout page
Calls create-mcc-payment with the amount, then window.location = paymentUrl.
4. Frontend `/checkout/return` page
Reads ?reference, ?error, ?cancelled from the query string. Polls pay-status every 3s up to 30 attempts (90s total) as the webhook fallback. Shows success / failed / cancelled / timeout state to the customer.
5. Two environment variables on the backend
MCC_SECRET = sk_test_proj_<...> (sandbox value for now)
MCC_WEBHOOK_SECRET = <test webhook secret>
Use GENERIC names (no _TEST_) so the live swap is a value change, not a code change.
6. The MCC project's "Webhook URL" field (the user sets this in MCC dashboard) must point at your deployed mcc-webhook endpoint.
== Critical gotchas ==
- Origin header MUST be set on the server-side fetch to MCC. Browsers add it automatically; servers do not.
- Raw body for HMAC verify, never the parsed-then-restringified payload.
- All three webhook event types (completed / failed / cancelled) must be handled — non-200 = infinite retry.
- `livemode` is `false` on sandbox payloads and ABSENT on live. Never gate logic with `livemode === true`.
- CORS preflight on create-mcc-payment — return CORS headers on OPTIONS, not just on POST.
- Omit customerEmail / customerName if absent; do not send empty string.
- amount is integer HTG between 1 and 1,000,000. Recommended test value: 100.
== Out of scope (do NOT do these) ==
- Live mode. Implement sandbox only. Live is one env swap later, not a code change.
- The merchant's own order persistence / inventory / fulfillment logic. Hand them the payment.completed event and let them wire it.
- Translating "Sandbox" to French if generating French copy. It's the product feature name and stays English (like Stripe).
== When done ==
Give the user:
1. The two env vars they need to set + the webhook URL they need to paste into the MCC project.
2. A 4-step test plan: open the simulator URL returned from pay-create, click Pay success on the MCC simulator, verify webhook arrives in your logs + your /checkout/return resolves to "completed".
3. A one-line note: going live = swap MCC_SECRET to sk_proj_<...> + MCC_WEBHOOK_SECRET to the live webhook secret, no code change.
Lovable — paste it in the chat as-is.
Claude / ChatGPT — same paste; the assistant will ask you for the three values.
Cursor / Copilot — open a chat panel in your repo, paste, answer the three questions when prompted.
What you'll need to provide
- Your domain (e.g.
mamasoil.shop) - Your
MCC_SECRET(starts withsk_test_proj_) - Your
MCC_WEBHOOK_SECRET(the test webhook secret)
After the AI implements and you deploy, come back to the next section to verify the API contract matches what was built.
1. Create a project
Sign in → Developer → Projets → Nouveau projet.
Fill in
| Nom / Code | Type / Limite | Description |
|---|---|---|
| Name | internal | For your reference — never shown to customers. |
| Webhook URL | HTTPS required | Where MCC will POST events. |
| Allowed domains | exact | Exact domains your backend will declare as Origin when calling MCC's API. Mismatch = 403. |
On submit, you'll see a one-time modal with FOUR values. Save them all:
| Nom / Code | Type / Limite | Description |
|---|---|---|
| Live publishable | pk_proj_<32 hex> | Optional — client-side rendering. |
| Live secret | sk_proj_<48 hex> | Bearer token for live payments. |
| Live webhook secret | whsec_<48 hex> | HMAC key for live webhook verification. |
| Test secret | sk_test_proj_<48 hex> | Bearer token for sandbox. |
The test publishable + test webhook secret are also surfaced on the Projects tab (the test webhook secret via "Régénérer les clés de test").
2. The MCC API
Three endpoints. Same for sandbox and live — only the bearer token changes.
2.1 Create a payment
https://hvlmeoqyxaguzcujpmit.supabase.co/functions/v1/pay-createHeaders:
Authorization: Bearer <YOUR_SECRET>Origin: https://YOUR-ALLOWED-DOMAINContent-Type: application/json
Body:
{
"amount": 100,
"referenceId": "<your-unique-id>",
"returnUrl": "https://YOUR-DOMAIN/checkout/return",
"customerEmail": "optional@example.com",
"customerName": "optional"
}Response (200):
{
"paymentUrl": "https://moncashconnect.com/sim/<reference>?p=<short>",
"reference": "<your-unique-id>",
"expiresAt": "2026-01-01T00:00:00Z",
"livemode": false
}Constraints
amount: integer HTG, 1 to 1,000,000.referenceId: your unique id (UUID recommended), 100 chars max,[a-zA-Z0-9\-_].returnUrl: must be HTTPS.Originheader is REQUIRED and must match Allowed domains.- Omit
customerEmail/customerNameif absent — empty strings trip validation.
Redirect the customer's browser to the returned paymentUrl. In sandbox: the MCC simulator. In live: the real MonCash page.
2.2 Receive webhooks
MCC POSTs to your webhook URL on every status change. Verify the HMAC-SHA256 signature before trusting the payload.
Headers:
Content-Type: application/jsonX-MCC-Signature: sha256=<hex>X-MCC-Timestamp: <unix seconds>
Body:
{
"event": "payment.completed" | "payment.failed" | "payment.cancelled",
"reference": "<your-unique-id>",
"amount": 100,
"status": "completed" | "failed" | "cancelled",
"completedAt": "2026-01-01T00:00:00Z" | null,
"livemode": false
}Verification
- Read the request body as RAW BYTES (do not parse JSON first — re-serializing breaks HMAC).
- Compute
sha256=<hex>ofHMAC-SHA256(your-webhook-secret, raw-body). - Compare against the
X-MCC-Signatureheader. - If match, parse the JSON and dispatch on
event.
livemode is false on sandbox payloads and ABSENT on live payloads. Don't gate logic with livemode === true — it will never be true.2.3 Poll payment status (fallback)
https://hvlmeoqyxaguzcujpmit.supabase.co/functions/v1/pay-status?reference=<your-unique-id>No auth required. Returns the same payment record. Useful for your return page when the webhook hasn't arrived yet.
3. Test in sandbox
3.1 Set up your environment
Two secrets in your backend's .env (or equivalent):
MCC_SECRET = sk_test_proj_<your-test-secret>
MCC_WEBHOOK_SECRET = <your-test-webhook-secret>MCC_SECRET, not MCC_TEST_SECRET). When you go live, you swap values — no code change.3.2 Trigger a sandbox payment
Recommended test amount: 100 HTG (~$0.75 USD). Same amount you'll use for the live verification later — small enough to keep both tests cheap, large enough to behave like a real payment.
From your checkout, call your pay-create endpoint. The response gives you a paymentUrl. Redirect the browser there.
3.3 What you'll see on the simulator
The browser opens https://moncashconnect.com/sim/<reference>?p=<short>. A centered card with:
- An amber SANDBOX badge
- "Test transaction — no real money"
- Your reference id
- Three buttons:
- Pay success (green) — flips status to
completed, credits the sandbox ledger. - Pay fail (red) — flips status to
failed, no credit. - Cancel / timeout (outline) — flips status to
cancelled, no credit.
- Pay success (green) — flips status to
- An explainer footer
Click any of them.
3.4 What happens
- Transaction status flips in MCC's database.
- Webhook fires to your endpoint with
livemode: falseand the matching event. - Browser redirects to
<your returnUrl>?reference=<id>(+&error=true&cancelled=truefor cancel,&error=truefor fail).
3.5 Where to verify in MCC
After clicking a simulator button:
MCC Dashboard → /transactions
Pill toggle at the top: En direct | Test. Click Test. Your sandbox payments appear with status, amount, in/out coloring, and reference. Sandbox txs are NEVER mixed with live; they're isolated.
Your webhook handler logs
One POST should be visible with a valid HMAC signature and the matching event type.
Your return page
Should reflect the outcome.
3.6 Run all three outcomes
| Nom / Code | Type / Limite | Description |
|---|---|---|
| Pay success | payment.completed | ?reference=<id> |
| Pay fail | payment.failed | ?reference=<id>&error=true |
| Cancel | payment.cancelled | ?reference=<id>&error=true&cancelled=true |
Trigger one fresh payment per outcome (each transaction can only be transitioned once).
4. Going live
After all three sandbox outcomes verify cleanly:
4.1 Swap two secret values
| Nom / Code | Type / Limite | Description |
|---|---|---|
| MCC_SECRET | sk_test_proj_… | Live: sk_proj_… |
| MCC_WEBHOOK_SECRET | test webhook secret | Live: live webhook secret |
4.2 What changes in live mode
| Nom / Code | Type / Limite | Description |
|---|---|---|
| paymentUrl | Sandbox: MCC simulator | Live: real MonCash URL (Digicel-hosted) |
| Customer flow | Sandbox: 3 buttons | Live: real MonCash page: phone, PIN, confirm |
| Settlement | Sandbox: instant | Live: ~5-30 seconds after PIN; SMS confirmation |
| livemode (webhook) | Sandbox: false | Live: absent |
| Money | Sandbox: none | Live: real HTG, ~3% commission |
| Dashboard tab | Sandbox: Test | Live: En direct (default) |
4.3 First live test
Recommended: the same 100 HTG amount you tested in sandbox.
- Click your checkout button. Browser opens a real MonCash URL.
- Enter your real MonCash phone number + PIN. Confirm.
- Browser redirects to your return URL with
?reference=… - Within 30-60 seconds, MCC's reconciler updates the transaction to
completedand fires the webhook. - Verify in MCC
/transactions(En direct tab) — your 100 HTG row,completed, ~3 HTG commission, ~97 HTG net.
4.4 Webhook delivery caveat
pay-status poll on your return page for up to 90 seconds. Your customer sees the confirmation immediately regardless of webhook timing.5. Common errors
| Nom / Code | Type / Limite | Description |
|---|---|---|
| 403 Origin not allowed | Origin header ≠ Allowed domains | Add domain to MCC project. |
| project_lookup_failed 502 | Wrong bearer token | Re-copy from MCC dashboard. |
| tx_not_found_or_not_pending | Tx already processed | Trigger fresh payment. |
| bad signature (webhook) | Used parsed JSON for HMAC | Use raw body bytes. |
| Sandbox webhook OK, live doesn't | Known issue, see §4.4 | Use pay-status polling fallback. |
| Browser console: Failed to fetch | CORS preflight on YOUR backend | Return CORS headers on OPTIONS. |
FAQ
Does sandbox cost anything?
No.
Can I skip KYC for sandbox?
Yes. Sandbox is open to any signed-in account — create a project, grab your sk_test_proj_… keys and start integrating right away. Identity verification (KYC) is only required to activate Live mode (sk_proj_…).
Are sandbox transactions visible to MCC admins?
No. Merchant's /transactions (Test tab) only. Never affects dashboard summaries, finance, or admin views.
Multiple projects?
Yes. Each gets its own live + test key pair + webhook URL.
Min / max amount?
1 to 1,000,000 HTG.
Does sandbox simulate MonCash's UI?
No — three buttons. Sandbox tests your integration, not Digicel's UI.
Webhook signature scheme?
HMAC-SHA256, X-MCC-Signature: sha256=<hex>. Same in sandbox and live.
Going live = redeploy?
No. Only environment variable values change.
Refunds?
Not exposed via public API yet. Contact MCC support.
Status
This guide is published as a public reference. If anything is unclear, contact MCC support with your project name, the function log snippet, and the reference UUID.