# Dreamo Pay — integration guide for developers and AI assistants Dreamo Pay accepts mobile money payments in Ghana (MTN MoMo, Telecel Cash, AT Money). The merchant's server creates a payment, redirects the customer to Dreamo Pay's hosted checkout page, and the customer approves a prompt on their phone with their PIN. API base URL: https://www.dreamopay.com/api/v1 Human-readable docs: https://www.dreamopay.com/docs ## Rules (read first) - Payments move REAL money. There is no sandbox. Test with a small amount (e.g. GHS 5.00). - The merchant must be approved by Dreamo Pay and must generate LIVE keys in Dashboard -> API Keys. Secret keys start with "sk_live_". Test keys ("sk_test_") cannot create payments. - The secret key must only be used on the server (e.g. env var DREAMO_SECRET_KEY). Never put it in browser, mobile or frontend code. - Amounts are integers in pesewas: 5000 = GHS 50.00. Only GHS is supported. - Never mark an order paid because the customer reached the redirect URL. Confirm with GET /payments/:reference (status "success") or a verified webhook. - Fulfil each payment reference only once (webhooks and status checks can both arrive). ## Authentication Header on every request: Authorization: Bearer sk_live_xxxxxxxx Rate limit: 120 requests per minute. ## Create a payment POST https://www.dreamopay.com/api/v1/payments Headers: Authorization: Bearer Content-Type: application/json Idempotency-Key: (optional; retrying with the same key returns the original payment) Body fields: amount integer, required. Pesewas (5000 = GHS 50.00). Max 100000000. description string, optional, max 200 chars. Shown on checkout. customerEmail string, optional, valid email. customerName string, optional, max 100 chars. redirectUrl string, optional. Full URL the customer returns to after a successful payment. reference string, optional, 4-64 chars, unique. Generated if omitted. metadata object, optional. Any JSON, e.g. {"orderId":"1234"}. Returned in GET and webhooks. currency optional, must be "GHS". Example: curl -X POST https://www.dreamopay.com/api/v1/payments \ -H "Authorization: Bearer sk_live_xxxxxxxx" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: order-1234" \ -d '{"amount":5000,"description":"Order #1234","redirectUrl":"https://yourapp.com/orders/1234","metadata":{"orderId":"1234"}}' Response 201: { "reference": "dp_9f3a1b2c4d5e6f708192", "amount": 5000, "currency": "GHS", "status": "pending", "description": "Order #1234", "customerEmail": null, "customerName": null, "paymentMethod": null, "feeAmount": 0, "netAmount": 0, "metadata": { "orderId": "1234" }, "failureReason": null, "checkoutUrl": "https://www.dreamopay.com/pay/dp_9f3a1b2c4d5e6f708192", "createdAt": "2026-10-16T12:00:00.000Z", "paidAt": null } Save "reference" with the order, then redirect the customer's browser to "checkoutUrl". ## Get a payment GET https://www.dreamopay.com/api/v1/payments/ Header: Authorization: Bearer Returns the same object as above. status values: pending created, customer has not paid yet processing prompt sent, waiting for the customer to approve on their phone success paid; feeAmount, netAmount, paidAt are set. Safe to fulfil. failed declined / not approved; see failureReason. Cannot be retried — create a new payment. ## List payments GET https://www.dreamopay.com/api/v1/payments?page=1 -> { "items": [...], "total", "page", "pageSize": 20 } ## Webhooks The merchant sets a webhook URL in Dashboard -> API Keys and copies the webhook secret ("whsec_...", env var DREAMO_WEBHOOK_SECRET). Dreamo Pay sends: POST Content-Type: application/json X-Dreamo-Signature: Verify the signature over the exact raw bytes (do not re-serialize parsed JSON). Compare with a constant-time comparison after checking the lengths match. Reject with 401 if invalid. Reply 2xx within 10 seconds. Each webhook is sent ONCE with no retries, so also confirm with GET /payments/ when needed. payment.success: {"event":"payment.success","data":{"reference":"dp_...","amount":5000,"currency":"GHS","netAmount":4850,"feeAmount":150,"customerEmail":null,"customerName":null,"paymentMethod":"momo","metadata":{"orderId":"1234"},"paidAt":"2026-10-16T12:01:30.000Z"},"createdAt":"2026-10-16T12:01:30.100Z"} payment.failed: {"event":"payment.failed","data":{"reference":"dp_...","amount":5000,"currency":"GHS","failureReason":"The payment was declined or not approved.","metadata":{"orderId":"1234"}},"createdAt":"2026-10-16T12:03:00.000Z"} Node.js (Express) verification: import crypto from "crypto"; app.post("/webhooks/dreamo", express.raw({ type: "application/json" }), (req, res) => { const signature = String(req.headers["x-dreamo-signature"] || ""); const expected = crypto.createHmac("sha256", process.env.DREAMO_WEBHOOK_SECRET).update(req.body).digest("hex"); const valid = signature.length === expected.length && crypto.timingSafeEqual(Buffer.from(signature), Buffer.from(expected)); if (!valid) return res.status(401).send("invalid signature"); const event = JSON.parse(req.body.toString("utf8")); if (event.event === "payment.success") { /* mark event.data.metadata.orderId paid, once */ } res.sendStatus(200); }); Python (Flask) verification: import hmac, hashlib, os @app.post("/webhooks/dreamo") def dreamo_webhook(): raw = request.get_data() expected = hmac.new(os.environ["DREAMO_WEBHOOK_SECRET"].encode(), raw, hashlib.sha256).hexdigest() if not hmac.compare_digest(expected, request.headers.get("X-Dreamo-Signature", "")): return "invalid signature", 401 event = request.get_json() # if event["event"] == "payment.success": mark order paid, once return "", 200 PHP verification: $raw = file_get_contents("php://input"); $expected = hash_hmac("sha256", $raw, getenv("DREAMO_WEBHOOK_SECRET")); if (!hash_equals($expected, $_SERVER["HTTP_X_DREAMO_SIGNATURE"] ?? "")) { http_response_code(401); exit; } $event = json_decode($raw, true); ## Errors JSON body: {"error":"","message":"...","details":{...}} (details only on validation errors) 400 validation_error invalid body; message/details explain 401 unauthorized missing or invalid secret key 403 pending_approval business not approved yet (or rejected) 403 live_keys_required approved but using a test key; generate live keys 403 forbidden account disabled 404 not_found no payment with that reference on this account 409 duplicate_reference reference already used 429 too_many_requests over 120 requests/minute; wait and retry ## Payouts Successful payments minus the fee are added to the merchant's Dreamo Pay balance. Payouts to the merchant's mobile money wallet are requested from the dashboard (Settlements page); there is no payout API for integrations.