All providers
Bictorys
payment·🇸🇳🇨🇮🇬🇳🇲🇱🇹🇬🇧🇯🇧🇫🇳🇪🇨🇲🇳🇬·Sandbox available
Bictorys is a payment infrastructure provider serving West and Central Africa, supporting mobile money and card payments across multiple countries through a unified API.
Use with AI agents
After installing the plugin or adding the MCP server, prompt your agent:
“Initiate a mobile money or card payment and redirect the customer to complete checkout.”
Install the plugin →Capabilities
| Capability | Type | StatusiVerified — tested in a real application.Available — spec complete, usable by agents. | Method | Example |
|---|---|---|---|---|
| create_charge | synchronous | Available | POST | Example |
| create_payout | synchronous | Available | POST | Example |
| verify_transaction | synchronous | Available | GET | Example |
| webhook_payment_completed | webhook | Available | POST | Example |
Gotchas
create_charge
- ⚠payment_type is a query parameter, not a request body field. Append it to the URL: POST /pay/v1/charges?payment_type=wave_money. Omitting it returns HTTP 202 with a hosted-checkout link instead of a direct 201 response.
- ⚠The response HTTP status determines the flow: 201 means the direct payment was initiated (use response.transactionId and response.redirectUrl); 202 means the customer must be sent to response.link on the Bictorys-hosted checkout page.
- ⚠Store transactionId (201 flow) or chargeId (202 flow) before redirecting the customer. Both are required to verify the payment server-side via verify_transaction.
- ⚠Always verify the transaction server-side via verify_transaction before fulfilling any order. Redirect callbacks and webhooks can be spoofed.
- ⚠successRedirectUrl and errorRedirectUrl must start with https:// — HTTP URLs are rejected with E400-0: redirectUrl must start with https://.
- ⚠Sandbox base URL is https://api.test.bictorys.com — the production base URL is https://api.bictorys.com. Sandbox and production use separate API keys obtained from the Bictorys dashboard.
- ⚠Card payments via the direct API (payment_type=card with a cardObject in the body) require PCI-DSS certification. Mobile money payments have no certification requirement.
- ⚠create_charge uses the PUBLIC API key (env BICTORYS_PUBLIC_KEY, header X-Api-Key). Using the private/secret key for charges returns 403. Payouts use the private key (BICTORYS_SECRET_KEY, header X-API-Key).
- ⚠Orange Money Côte d'Ivoire requires an OTP: the customer must dial #144*82# on their phone to generate a 6-8 digit code, then pass it as the 'otp' field in the request body. The OTP expires quickly — if the charge fails with an OTP error, ask the user to redial.
- ⚠The request body field is 'errorRedirectUrl' (lowercase e), but Bictorys echoes it back as 'ErrorRedirectUrl' (capital E) in some response and webhook contexts. Use lowercase when building the request.
- ⚠HTTP 403 with an HTML body (not JSON) means AWS WAF is rate-limiting your requests. Implement exponential backoff with at least 5-second spacing between retries.
create_payout
- ⚠payment_type is a required query parameter. Append it to the URL: POST /pay/v1/payouts?payment_type=wave_money. Supported values: wave_money, orange_money, mtn_money, moov.
- ⚠transactionType is required — omitting it causes a 500. Use "payment" for standard disbursements.
- ⚠idempotency-key header is effectively required — a missing idempotency-key is a known cause of 500 errors. Always send a UUID per request.
- ⚠merchant.secretCode is the operator-issued merchant PIN (e.g. your Orange Money or MTN merchant code) registered in Bictorys Dashboard → Entreprise → Préférences. Required when configured on your account — omitting it returns E400-37: Secret code is missing.
- ⚠customerObject.phone is a string with country code prefix, no + sign, no spaces. Example: "221770000000" for Senegal. Using a + prefix or a numeric type causes a 400 phone error.
- ⚠The response amount is a negative integer — money leaving the merchant account. Use Math.abs() if displaying to the user.
- ⚠The response status field is an integer (0 = success), not a string like other Bictorys endpoints.
- ⚠Money transfer must be explicitly enabled for your merchant account. If not, Bictorys returns E400-28: Money transfer is not authorized for this merchant. Contact Bictorys support.
- ⚠Set your HTTP client timeout to at least 30 seconds — payout processing is slower than charge creation.
- ⚠Sandbox base URL is https://api.test.bictorys.com — the production URL is https://api.bictorys.com. Sandbox payouts do not send real money.
verify_transaction
- ⚠Always call this endpoint server-side to verify the transaction before fulfilling any order. Never rely solely on redirect query parameters or webhook payloads — both can be forged.
- ⚠Only status === "succeeded" means the payment is fully complete and funds are captured. "authorized" means funds are reserved but not yet captured. "pending" and "processing" mean the payment is still in progress.
- ⚠The transactionId to pass here is the id field from the webhook payload, or the transactionId / chargeId field from the create_charge response — they refer to the same transaction UUID.
- ⚠Sandbox base URL is https://api.test.bictorys.com — the production URL is https://api.bictorys.com.
- ⚠The /status endpoint returns a minimal response in sandbox: only {id, status} when the transaction is pending. Fields like pspName, amount, timestamp, and customer info are populated once the payment completes. In production, the full object is returned.
- ⚠In the Bictorys sandbox, GET /pay/v1/transactions/{id}/status is known to return HTTP 500 intermittently. For sandbox testing, rely on webhook events instead of polling this endpoint.
webhook_payment_completed
- ⚠Bictorys sends two validation headers: X-Secret-Key (raw shared secret, always present) and optionally X-Webhook-Signature (HMAC-SHA256 hex) + X-Webhook-Timestamp (Unix milliseconds). When X-Webhook-Signature is present, verify with HMAC-SHA256(BICTORYS_WEBHOOK_SECRET, '${timestamp}.${rawBody}') and reject payloads where Math.abs(Date.now() - timestamp) > 300_000.
- ⚠Always use crypto.timingSafeEqual() when comparing X-Secret-Key or HMAC values — never use === or ==. String equality comparison is vulnerable to timing attacks.
- ⚠Bictorys retries the webhook up to 3 times if your endpoint returns a non-2xx status. Always respond HTTP 200 immediately, even if your internal processing fails — then handle errors asynchronously.
- ⚠Express users: mount express.raw({ type: 'application/json' }) before express.json() on the webhook route. The raw string body is required to compute the HMAC; JSON.parse() loses it.
- ⚠Never fulfill an order based on the webhook payload status alone. Always call verify_transaction server-side with the payload's id field to confirm the payment before marking an order as paid.
- ⚠The status field in the webhook payload is UPPERCASE (SUCCEEDED, FAILED) whereas verify_transaction returns lowercase (succeeded, failed). Do not compare these directly without normalizing case.
- ⚠Implement idempotency using the transaction id field. Bictorys may deliver the same webhook event more than once; processing the same id twice must not double-fulfill an order.
- ⚠Your webhook URL must be publicly reachable over HTTPS. Webhook configuration is separate for test vs. production environments in the Bictorys dashboard.
Details
- Category
- payment
- Sandbox
- Yes