Central Africa's Payment Infrastructure API.
One API to collect payments from customers, disburse payouts to recipients, and access a growing catalog of value-added services across mobile money and digital wallets in Africa.
Payments & e-commerce
Accept payments from customers and disburse funds to recipients via mobile money. Simplest integration.
Bills, top-ups & more
Utility bills, airtime top-ups, vouchers, and subscriptions. Dynamic catalog per customer.
Key concepts
The S3P API uses a dynamic service model. Two top-level categories cover all use cases — but 90% of integrations only need one of them.
Collections & Disbursement
- Collection — collect payment from a customer's mobile wallet via
/cashout - Disbursement — disburse funds into a recipient's mobile wallet via
/cashin - Static catalog — cache once at startup
- Fewest steps, fastest to integrate
Value-added services
- Bills — electricity, water, internet (per-customer lookup)
- Top-ups — airtime and data (static catalog)
- Vouchers — return a redeemable PIN on purchase
- Subscriptions — recurring service payments
Vocabulary
Throughout this guide we use merchant-perspective business terms. Each one maps to a single catalog endpoint you call once at startup to discover the available payment packages.
Authentication
The S3P API uses standard OAuth 2.0 with the client_credentials grant. Trade your publicKey and secretKey for a short-lived JWT bearer token, then send it on every request — every standard HTTP client and every Authorization: Bearer …-aware tool works as-is.
publicKey / secretKey in client-side code, mobile binaries, logs, or version control. Mint tokens server-side and forward only the short-lived bearer downstream.OAuth 2.0 (client_credentials)
Two-step flow: exchange your publicKey and secretKey for a short-lived JWT access token at /oauth/token, then attach Authorization: Bearer … on every API request. Once minted, only the bearer travels with each call — your credentials stay on your server.
-
Request a token
Send
publicKey:secretKeyas HTTP Basic credentials.grant_typeis optional; if present it must beclient_credentials.POST {YOUR_API_HOST}/oauth/tokenhttpPOST /oauth/token HTTP/1.1 Host: {YOUR_API_HOST} Content-Type: application/x-www-form-urlencoded Authorization: Basic <base64(publicKey:secretKey)> grant_type=client_credentials200 OKjson{ "access_token": "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9...", "token_type": "Bearer", "expires_in": 3600 }ℹ️Readexpires_inoff every responseToken TTL is set per-response — don't hard-code it. Refresh roughly 60s early to absorb clock skew. -
Attach as a bearer header
examplehttpAuthorization: Bearer eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9...Send this header on every secured endpoint. The
publicKey:secretKeypair stays out of the request — only the short-lived JWT travels. -
Refresh before expiry
client_credentialstokens are not refreshable — request a new one when the current one expires. Tracknow + expires_inclient-side and refresh ~60s early.
expThat's the OAuth 2.0 contract (RFC 6749 §1.4). Cache the bearer and reuse it until ~60s before exp — don't mint a fresh one per request.Collections & Disbursement
The primary integration category. Collect payments from customers' mobile wallets, or disburse funds to recipients. Payment packages are static — cache once, then quote → execute → verify.
Accept a payment
Money flows out of the customer's wallet and into your Maviance account.
/cashout
Make a payout
Money flows from your Maviance account into the customer's wallet.
/cashin
/cashout = the Collection catalog (money in to you). /cashin = the Disbursement catalog (money out from you). Both are GET discovery endpoints — call once, cache the payItemId values, then quote and execute against them. You don't need to think about the names again after that.Watch the flow run end to end.
Two flows, same four-step pattern: quote, execute, customer step, callback. Each stage below shows the customer device on the left and your server's HTTP traffic on the right. Press Run flow to step through it. The default outcome path is callback-driven — we POST the final status to the URL configured on your account; polling /verifytx is only the fallback if a callback hasn't arrived within your SLA.
Collection
money in-
1Quote the price
-
2Execute the collection
-
3Customer approves on phone
-
4Receive callback
Payout
money out-
1Quote the payout
-
2Execute the disbursement
-
3Provider routes to recipient
-
4Receive callback
The flow above is the same primitive you'll use for collections, payouts, and most VAS calls — only the discovery endpoint and the package's payItemId change. Read the accept-a-payment and send-a-payout guides for full request/response details.
Accept a payment money flows out of customer's wallet
Four steps: get a quote using a cached package from /cashout, execute the collection, wait for the customer to approve on their phone, then receive a callback with the final status. GET /verifytx is the fallback if no callback arrives within your SLA.
-
Cache collection packages (once at startup)
Fetch available collection packages. This is a static catalog — cache it and refresh periodically.
GET /v2/cashoutjson[{ "payItemId": "S-1-10-CMMTNMOMO-90001-500100-1", "amountLocalCur": null, // null = variable amount "localCur": "XAF", "name": "MTN Mobile Money Collection" }] -
Request a quote
Use the cached
payItemIdand the amount the customer will pay.⏱️Quotes expire and are single-useExecute the collection immediately — the quoteId expires after a short window.POST /v2/quotestdjson{ "payItemId": "S-1-10-CMMTNMOMO-90001-500100-1", "amount": 10000 } // Response { "quoteId": "Q-8821-cde456", "priceLocalCur": 10000, "localCur": "XAF" } -
Execute the collection
Submit the quoteId and the customer's mobile wallet number. Smobilpay sends an approval prompt to their phone. Store the returned PTN immediately.
POST /v2/collectstdjson{ "quoteId": "Q-8821-cde456", "serviceNumber": "237700000001", "customerPhonenumber": "237700000001", "trid": "order-00123" } // Response — store PTN immediately { "ptn": "99999164577717700069821234567890", "status": "PENDING" } -
Receive the callback (and fall back to polling if needed)
Once the customer approves on their phone, Smobilpay
POSTs the final status to the callback URL configured on your account. Acknowledge with a 2xx within ~5 seconds; non-2xx responses are retried. If a callback hasn't arrived within your SLA window, fall back toGET /verifytx?trid=<your-trid>for reconciliation (rate limit: at most once every 10 seconds per transaction).🚨Only fulfil the order on SUCCESSPENDING means the customer has not yet approved. Never release goods or credit until you see SUCCESS.Transaction status
Status Meaning Action PENDING Awaiting customer approval Wait for callback (or poll as fallback) SUCCESS Customer approved — funds received Fulfil order ERRORED Declined, timed out, or wrong PIN Check errorCode, notify customer REVERSED Rare - Reversed during reconciliation Contact support
Make a payout money flows into recipient's wallet
A payout moves funds from your Maviance account to a recipient's mobile wallet using /cashin packages. No customer approval needed — funds land immediately on success and Smobilpay POSTs the final status to your callback URL.
GET /verifytx?trid=... to check if the original payout succeeded. Never pay a recipient twice.The flow mirrors collections, just using /cashin packages. Quote with the payout amount, execute with the recipient's wallet, then receive the callback when funds settle. Fall back to GET /verifytx?trid=... for reconciliation or before any retry. Payouts typically settle faster than collections.
POST /v2/collectstd — payout examplejson{ "quoteId": "Q-9103-fgh789", "serviceNumber": "237680000002", // recipient's wallet "customerPhonenumber": "237680000002", "trid": "payout-ref-456" }
Value-added services
Bill payments, top-ups, vouchers, and subscriptions. These require a per-customer lookup step before quoting — the payable item is dynamic per customer or service number.
| Type | Endpoint | Lookup frequency | Notes |
|---|---|---|---|
SEARCHABLE_BILL | GET /bill | Per customer, on demand | Returns all open bills |
NON_SEARCHABLE_BILL | GET /bill | Per customer, on demand | Returns a single bill item |
TOPUP | GET /topup | At startup / periodic | Static catalog |
VOUCHER | GET /voucher | At startup / periodic | Returns redeemable PIN on purchase |
PRODUCT | GET /product | At startup / periodic | Static catalog |
SUBSCRIPTION | GET /subscription | Per customer, on demand | Tied to a customer account |
VAS flow
payItemId values from the bill lookup can become invalid if too much time passes before quoting. Complete the flow promptly.Endpoint reference
All endpoints require a valid Authorization header.
Collections & Disbursement
Returns available payment packages for accepting money from customers. Cache at startup, refresh periodically.
| Parameter | Required | Description |
|---|---|---|
| merchant | optional | Filter by merchant code (e.g. CMMTNMOMO) |
| serviceid | optional | Filter by service ID |
Returns available packages for disbursing funds to recipients. Cache at startup, refresh periodically.
Value-added services
Returns open bills for a customer's account. Each result includes a payItemId for quoting. Called on-demand per customer.
| Parameter | Required | Description |
|---|---|---|
| merchant | required | Merchant code |
| serviceid | required | Service ID |
| serviceNumber | required | Customer's account/meter number |
Returns available airtime and data top-up packages. Cache at startup.
Returns the fixed product catalog. Cache at startup.
Returns available vouchers. A successful collection returns a redeemable PIN in the pin field of the POST /collectstd response.
Returns subscriptions for a customer account. Each has its own payItemId. Called on-demand per customer.
Shared
200 OKjson{ "status": "OK", "version": "3.2.0" }
Returns your current agent balance in XAF. Insufficient balance blocks all collections and payouts (error 40201). Monitor regularly.
Returns all available merchants. Cache and refresh daily — new providers are added without API changes.
Returns services under a merchant. The serviceType tells you whether it's a collection, payout, bill, etc. Check isVerifiable for pre-validation support.
For services where isVerifiable: true, returns a boolean indicating whether the supplied serviceNumber is valid for the selected service and merchant. Lightweight existence check — does not return customer identity data. Reduces failed transactions when used before /quotestd.
Validates an account (destination + serviceId) against the upstream service provider and, when available, returns the associated CustomerAccount including the customer's name. Unlike /verify, this endpoint returns personally identifiable information.
401. Contact your integration manager to request enablement.Used by all service types. Returns the exact cost and a single-use quoteId. Execute immediately — quotes expire quickly.
| Field | Required | Description |
|---|---|---|
| payItemId | required | From /cashin, /cashout, or VAS discovery |
| amount | required | Transaction amount in local currency |
Executes any payment type. Returns a PTN — store immediately. The serviceNumber is the customer's wallet (collections) or recipient's wallet (payouts).
| Field | Required | Description |
|---|---|---|
| quoteId | required | Single-use, from POST /quotestd |
| serviceNumber | required | Customer wallet, recipient wallet, or provider account |
| customerPhonenumber | conditional | Required for most collections and payouts |
| trid | optional | Your unique reference — enables idempotency |
Get the current status of any payment by PTN or your trid. Use this as a fallback when a callback hasn't arrived within your SLA, or before any retry to confirm idempotency. Don't poll faster than once every 10 seconds per transaction.
Paginated transaction history. Only one filter parameter per request (error 40011 if multiple).
Error codes
All HTTP errors return a JSON object. Use respCode for logic, devMsg for debugging, usrMsg is safe to surface to customers.
error response formatjson{ "respCode": 4009, "devMsg": "Access token invalid", "usrMsg": "Authentication error", "link": "<support-url>" }
Authentication
| Code | HTTP | Meaning | Fix |
|---|---|---|---|
| 4000 | 400 | Authorization header malformed | Review header format |
| 4001 | 400 | Authorization header missing | Add Authorization header |
| 4005 | 400 | Nonce already used | Generate fresh unique nonce per request |
| 4009 | 400 | Access token invalid / bearer rejected | Mint a fresh token via POST /oauth/token; verify credentials in the admin portal. |
Account & limits
| Code | HTTP | Meaning | Fix |
|---|---|---|---|
| 40201 | 400 | Insufficient account balance | Top up agent account |
| 40202 | 400 | Collection limit exceeded | Contact support to raise limit |
| 60020 | 400 | Outside configured work hours | Retry during work hours |
| 40011 | 400 | Multiple filter params on /historystd | Use only one filter parameter |
Execution errors
These appear in POST /collectstd responses as validation errors before the payment is processed.
| Code | Meaning | Fix |
|---|---|---|
| 4204 | Invalid phone/wallet number format | Correct format and retry |
| 40305 | Destination blocked (suspected fraud) | Contact support |
| 40609 | payItemId already used or invalid | Re-fetch payItemId; restart flow |
| 40611 | Service under maintenance | Retry later |
| 40408 | Service does not support /verify | Check isVerifiable before calling |
| 41004 | Provider gateway unavailable | Retry with exponential backoff |
| 44001 | AML single-transaction limit | Reduce transaction amount |
Payment errors (in errorCode)
These appear in callbacks or GET /verifytx responses when status: "ERRORED".
| Code | Meaning | Customer-facing action |
|---|---|---|
| 703201 | Approval prompt timed out | Ask customer to retry |
| 703202 | Customer declined | Notify customer; offer retry |
| 703203 | Wrong PIN/OTP | Ask customer to retry |
| 703107 | Customer wallet insufficient funds | Inform customer to top up |
| 703103 | Wallet blocked | Customer should contact provider |
| 702102 | Amount below provider minimum | Increase amount |
| 702103 | Amount above provider maximum | Decrease or split |
| 703113 | Bill already paid | Restart from bill discovery |
| 5000 | Internal server error | Retry with exponential backoff |
SDKs & libraries
Six official Maviance clients (v3.2.0) cover the partner API end-to-end with OAuth 2.0 client_credentials built in — token mint, refresh, and Authorization: Bearer … attachment are handled for you. All six share the same endpoint coverage and a common smoke-test harness so cross-language behaviour stays in lock-step. If your language isn't listed, any standard HTTP client works as-is once you mint a token at POST /oauth/token.
Testing your integration
Use the staging environment to validate your full integration before requesting production access.
Staging environment
| Setting | Value | Notes |
|---|---|---|
| Base URL | issued during onboarding | Maviance shares the staging base URL once your account is provisioned |
| Credentials | Separate staging token & secret | Never use production credentials on staging |
| Data | Test merchants only | No real money moves |
Testing checklist
- ✓Authentication works —
GET /pingreturns 200 - ✓Collection packages load —
GET /cashoutreturns payItemId values - ✓Disbursement packages load —
GET /cashinreturns payItemId values - ✓Collection full flow — quote → execute → callback → SUCCESS
- ✓Payout full flow — quote → execute → callback → SUCCESS
- ✓Callback fallback works —
GET /verifytx?trid=...returns the same final status when callback is unreachable - ✓Customer decline handled — error 703202 shows correct message
- ✓Idempotency works —
GET /verifytx?trid=...before any retry - ✓PTN is stored — DB records PTN immediately on execution response
Common mistakes
- Crediting on PENDING — never fulfil until verifytx returns SUCCESS.
- Duplicate payouts — a network error doesn't mean failure. Always check
/verifytx?trid=...first if recovering from a network/timeout error - Hard-coded token TTL — read
expires_inoff every/oauth/tokenresponse and refresh ~60s before expiry. Hard-coding a value (e.g. 3600) breaks if the TTL is ever shortened. - Bearer rejected (4009) — mint tokens only via
POST /oauth/token. Bearers obtained any other way will not authenticate. - Clock drift — JWT
expis checked against the server clock. Hosts with >5 min NTP drift may see tokens rejected as expired sooner than expected.
Callbacks & transaction status
After executing a payment via POST /collectstd, the transaction enters PENDING. Smobilpay resolves it asynchronously. The recommended pattern is to wait for a callback, falling back to polling only if it doesn't arrive or if the network request encountered an interruption/timeout.
Recommended · Callback
- Register a webhook URL with Maviance (once)
- After
POST /collectstd, wait for Smobilpay to push the final status to your server - Process SUCCESS or ERROR immediately on receipt
- If no callback arrives within your timeout window, fall back to polling
Fallback · Polling
- Poll
GET /verifytx?ptn=...at most every 10 seconds - Stop polling as soon as status is no longer PENDING
- Do not poll continuously — violations can trigger account suspension
- Store the PTN before starting the poll loop
- Note that we are monitoring polling behavior to prevent abuse
GET /verifytx faster than once every 10 seconds per transaction. Excessive polling can result in your account being suspended without warning.Webhook setup
Webhooks are an opt-in feature. To enable, contact the Maviance ops team and provide the following:
| Field | Required | Description |
|---|---|---|
| URL | required | Publicly accessible HTTPS endpoint. If your server is behind a firewall, whitelist Smobilpay's IP addresses (request the list from ops). |
| Secret | optional | Shared secret used to sign every callback. Strongly recommended — without it the X-Signature header is empty and you cannot verify authenticity. |
| Skip SSL Verification | optional | Set true only if your endpoint uses a self-signed certificate. Not recommended for production. |
Delivery format
When a transaction reaches a final state (SUCCESS, ERRORED, or REVERSED), Smobilpay sends an HTTP POST with a JSON body to your configured URL. Delivery is at-least-once: transient failures are retried with exponential backoff and jitter (default 5 attempts).
Request headers
| Header | Description |
|---|---|
| X-Delivery | UUIDv4 generated per delivery. Stable across automatic retries of the same delivery, so you can deduplicate without parsing the body. Format: 36 chars, [0-9a-f-]. |
| X-Ptn | The PTN of the transaction whose status changed. Match this against your stored PTN. |
| X-Signature | Lowercase-hex HMAC-SHA1 of the raw request body, keyed with your configured secret. 40 chars of [0-9a-f]. Empty string if no secret is configured — treat empty as "unsigned", not "invalid". |
| Content-Type | Always application/json; charset=UTF-8 exactly. |
| Accept | Always application/json. |
Request body
Field order on the wire is fixed: timestamp, trid, errorCode, status. All four are always present (no omitempty). Producers and merchants are advised to normalize strings to Unicode NFC before UTF-8 encoding so the HMAC-SHA1 signature is stable across implementations.
| Field | Description |
|---|---|
| timestamp | Payment time as YYYY-MM-DD HH:MM:SS (no T separator, no timezone suffix — typically UTC at Smobilpay). Not RFC 3339; legacy contract. |
| trid | Your reference passed to POST /collectstd. Empty string if you didn't supply one. |
| errorCode | Numeric error code as a string. "0" on SUCCESS (and on PENDING), "3" on REVERSED, a non-zero domain-specific code on ERRORED. See Error codes. |
| status | One of SUCCESS, ERRORED, REVERSED, PENDING (PENDING callbacks are rare — payment is still in flight). |
Example callback
POST /your-webhook-endpointhttpPOST /exampleEndpoint HTTP/1.1 Host: your-server.example.com Content-Type: application/json; charset=UTF-8 Accept: application/json X-Delivery: fe6f0b85-0b3a-4f62-9f89-d7b3a18a8b18 X-Ptn: 99999152778369900057856272351928 X-Signature: 4ba0a40bff4fc5370f45e29f42a7a07bffacee26 {"timestamp":"2018-05-31 16:21:40","trid":"13550","errorCode":"0","status":"SUCCESS"}
Verifying the signature
Recompute the HMAC-SHA1 hex digest of the raw request body using your configured secret, then compare it to X-Signature. Use a constant-time comparison to prevent timing attacks.
Reference example
signature inputstext// Raw body to sign (exact bytes — no whitespace, fixed field order): {"timestamp":"2018-05-31 16:21:40","trid":"13550","errorCode":"0","status":"SUCCESS"} // Secret: secret // HMAC-SHA1 hex digest: 4ba0a40bff4fc5370f45e29f42a7a07bffacee26
Verification snippets
Express — capture raw bodyjavascriptconst express = require('express'); const crypto = require('crypto'); const app = express(); // Capture raw bytes — DO NOT use express.json() before signing. app.use(express.raw({ type: 'application/json' })); app.post('/webhook', (req, res) => { const raw = req.body; // Buffer const received = req.get('X-Signature') || ''; const expected = crypto .createHmac('sha1', process.env.SMOBILPAY_WEBHOOK_SECRET) .update(raw) .digest('hex'); if ( received.length !== expected.length || !crypto.timingSafeEqual(Buffer.from(received), Buffer.from(expected)) ) { return res.status(401).send('invalid signature'); } const body = JSON.parse(raw.toString()); const ptn = req.get('X-Ptn'); const delivery = req.get('X-Delivery'); // Idempotency: skip if delivery already processed if (alreadyProcessed(delivery)) return res.sendStatus(200); handleStatusChange(ptn, body.trid, body.status, body.errorCode); markProcessed(delivery); res.sendStatus(200); // 2xx tells Smobilpay it was received });
Flaskpythonimport hmac, hashlib, os from flask import Flask, request, abort app = Flask(__name__) SECRET = os.environ["SMOBILPAY_WEBHOOK_SECRET"].encode() @app.post("/webhook") def webhook(): raw = request.get_data() # bytes received = request.headers.get("X-Signature", "") expected = hmac.new(SECRET, raw, hashlib.sha1).hexdigest() if not hmac.compare_digest(received, expected): abort(401) body = request.get_json(force=True) ptn = request.headers["X-Ptn"] delivery = request.headers["X-Delivery"] if already_processed(delivery): return "", 200 handle_status_change(ptn, body["trid"], body["status"], body["errorCode"]) mark_processed(delivery) return "", 200
PHPphp$raw = file_get_contents('php://input'); $received = $_SERVER['HTTP_X_SIGNATURE'] ?? ''; $expected = hash_hmac('sha1', $raw, getenv('SMOBILPAY_WEBHOOK_SECRET')); if (!hash_equals($expected, $received)) { http_response_code(401); exit('invalid signature'); } $body = json_decode($raw, true); $ptn = $_SERVER['HTTP_X_PTN']; $delivery = $_SERVER['HTTP_X_DELIVERY']; if (already_processed($delivery)) { http_response_code(200); exit; } handle_status_change($ptn, $body['trid'], $body['status'], $body['errorCode']); mark_processed($delivery); http_response_code(200);
Spring Bootjava@PostMapping("/webhook") public ResponseEntity<String> webhook( @RequestHeader("X-Signature") String received, @RequestHeader("X-Ptn") String ptn, @RequestHeader("X-Delivery") String delivery, @RequestBody byte[] raw, @Value("${smobilpay.webhook.secret}") String secret ) throws Exception { Mac mac = Mac.getInstance("HmacSHA1"); mac.init(new SecretKeySpec(secret.getBytes(StandardCharsets.UTF_8), "HmacSHA1")); StringBuilder hex = new StringBuilder(); for (byte b : mac.doFinal(raw)) hex.append(String.format("%02x", b)); String expected = hex.toString(); if (!MessageDigest.isEqual(expected.getBytes(), received.getBytes())) { return ResponseEntity.status(401).build(); } // parse, deduplicate by delivery, dispatch... return ResponseEntity.ok().build(); }
net/httpgofunc webhook(w http.ResponseWriter, r *http.Request) { raw, _ := io.ReadAll(r.Body) received := r.Header.Get("X-Signature") mac := hmac.New(sha1.New, []byte(os.Getenv("SMOBILPAY_WEBHOOK_SECRET"))) mac.Write(raw) expected := hex.EncodeToString(mac.Sum(nil)) if !hmac.Equal([]byte(received), []byte(expected)) { http.Error(w, "invalid signature", 401) return } var body struct { Timestamp string `json:"timestamp"` Trid string `json:"trid"` Status string `json:"status"` ErrorCode string `json:"errorCode"` } json.Unmarshal(raw, &body) delivery := r.Header.Get("X-Delivery") if alreadyProcessed(delivery) { w.WriteHeader(200) return } handleStatusChange(r.Header.Get("X-Ptn"), body.Trid, body.Status, body.ErrorCode) markProcessed(delivery) w.WriteHeader(200) }
Receiver checklist
- ✓HTTPS only — accept callbacks over TLS; reject plain HTTP.
- ✓Verify the signature on every request before doing anything else. Use constant-time comparison.
- ✓Sign the raw body — capture bytes before any JSON parsing or middleware.
- ✓Deduplicate by
X-Delivery— UUID is stable across automatic retries within one delivery, so dedup is safe without parsing the body. For logical idempotency across requeues, also key on(X-Ptn, trid). - ✓Match by
X-Ptnortridagainst your stored transaction record. - ✓Return 2xx fast — any 2xx is treated as success and stops retries. Return
5xx,408, or429if you want the delivery retried; other 4xx responses are recorded for audit but not retried. Acknowledge within a few seconds, then process asynchronously if work is heavy. - ✓Always keep polling as a fallback — if a callback does not arrive within your SLA, fall back to
GET /verifytx. Service providers occasionally experience service delays or interruptions, which can cause delays in fullfillment on the Smobilpay platform.
Going to production
Going live requires completing the Partner Integration Process — a structured onboarding journey managed jointly by you and Maviance.
Partner integration process
-
Kick-off workshop
API presentation, overview of functions, Q&A.
-
Test account setup
Maviance sets up your partner account on the test environment and shares test credentials.
-
Integration
You perform the integration. Maviance provides live support.
-
Validation
Maviance validates and certifies that the integration meets requirements.
-
Go live
Maviance sets up your partner account on production and shares production credentials.
Certifying your integration
Before production access is granted, Maviance performs a full User Acceptance Test in four stages.
- Implementation completed — partner finishes implementation.
- Partner self-certification UAT — provide logfiles and a completed test scenario document.
- Partner provides Maviance access — integration in test system, Maviance support as needed.
- Maviance final UAT — functional compliance to business processes & technical compliance to API restrictions.
Integration policies
During certification Maviance verifies compliance with the following policies. Violations in production can result in automatic account suspension without warning.
| Endpoint | Reason | Recommended call frequency |
|---|---|---|
GET /account | Account balance is returned in the payment collection response — no need to constantly re-request it. | On demand |
GET /verifytx | Fallback only — use when a callback hasn't arrived within your SLA, or before any retry for idempotency. Don't call excessively for the same payment. | ≤ once / 10s per tx |
| Master data /merchant, /service, /cashin, /cashout, /topup, /product, /voucher | Master data does not change often and should be cached. | Daily |
Production security checklist
- ✓HTTPS only — never transmit bearer tokens or credentials over HTTP in production.
- ✓Credentials stay server-side — never expose your
publicKey/secretKey(or a minted bearer) in client code, mobile binaries, or version control. - ✓Store every PTN immediately — write PTN to DB before responding to the customer.
- ✓Idempotent retries via trid — always call
GET /verifytx?trid=...before retrying. - ✓Callbacks over polling — never faster than every 10 seconds per transaction.
- ✓Cache master data — refresh once daily.
- ✓Monitor account balance — set low-balance alerts; error
40201blocks all payments. - ✓Rotate credentials regularly — support zero-downtime rotation via the admin portal.