Security and help
FAQ and troubleshooting
Answers to the questions integrations raise, and what to check when something does not work.
General
Which currencies and cards are supported?
Payments are in Algerian dinars (DZD) only. Payers pay with CIB and Edahabia cards, processed by SATIM.
How much does Raïs cost?
A paid payment costs SATIM's fee (0.7% of the amount, never less than 10.00 DA and never more than 300.00 DA) plus Raïs's (0.3%); a refund costs 10.00 DA. There is no subscription and no setup fee, a failed payment costs nothing, and every figure excludes VAT. See Pricing and fees.
What are the smallest and largest payments?
The smallest payment is 50.00 DA (5000 centimes), SATIM's minimum. The largest is 999999999999 centimes. The smallest refund is 1.00 DA.
Can I call the API from a browser or a mobile app?
No. Secret keys must never leave your server, and the API sends no CORS headers on purpose. Create payments on your server and give the browser or the app the checkout_url. See Mobile and app integrations.
Where do I create keys and webhook endpoints?
In the dashboard, under Developers: API keys, Webhooks and Events.
Payments
Why do I get invalid_amount?
Amounts are integers in centimes. 5000.5, "5000" and 1500.00 are refused. To charge 1,500 DA, send 150000.
Why do I get parameter_unknown?
The request has a field the API does not know, usually a typo. The param in the error names it.
How long can a payer take to pay?
A payment stays payable for 24 hours by default. Set expires_in to anything from 300 seconds to 30 days. After that its status is expired and it cannot be paid.
The payer came back but the payment is still pending
Your server may be asking before the result has reached Raïs. Retrieving a pending payment asks the gateway for the outcome, so retry after a moment, and rely on the webhook. If the payer abandoned the page, it stays pending until it expires. Nothing is sent when it expires: see the next question.
How do I know that a payment expired?
There is no event for it. A payment that nobody paid reads as expired as soon as expires_at has passed: retrieve it, or compare expires_at with the clock. Close the matching order in your own system at that time.
The payer paid and closed the page. Will I still be told?
Yes. Raïs asks the gateway about unpaid payments in the background, starting about two minutes after the payment session opened and for up to 48 hours, and sends payment.succeeded when the gateway says it was paid. See Payments.
My filter has no effect
Unknown query parameters are ignored, so GET /payments?stauts=paid returns every payment. Check the spelling: the filters are status, reference, created_gte and created_lte.
A payment failed. Can the payer try again?
It depends on why it failed. If the bank declined the card (failed with failure.code: payment_declined), the payment is final: create a new payment, with a new Idempotency-Key. If the payer only left the page, pressed Back to or met an error before paying, the payment is still pending: the same checkout_url keeps working until expires_at, and the result page offers a Try again button. A cancelled or expired payment is final too.
Can I send the payment link by e-mail or SMS?
Yes. checkout_url is a plain link to the hosted page: share it with the payer. Anyone who holds it can pay it, so send it only to the payer.
Webhooks
The signature does not match
The usual causes, in order:
- You verified parsed and re-serialised JSON instead of the raw body. Read the bytes before any parsing.
- A proxy or a framework changed the body (decoded it, re-encoded it, or added a newline).
- You used the secret of another endpoint or another environment: sandbox and live endpoints have different secrets.
- You compared the whole header with the hex digest. The header has
t=andv1=parts. - Your clock is more than 300 seconds off.
Test your code with the official test vectors.
No webhook arrives
Check, in this order: the endpoint is enabled (after 20 consecutive failed deliveries it is switched off); it subscribes to the event type; its environment matches the payment (sandbox endpoints get sandbox events only); the address is reachable from the internet and not localhost or a private address; a live endpoint uses https://; and your server answers with 2xx and not a redirect. The delivery log of the dashboard shows each attempt and what your server answered.
I received the same event twice
Delivery is at least once. De-duplicate on the event id. See Webhooks.
How long do retries go on?
Up to 8 attempts over about 20 hours, then the delivery is marked failed and can be resent from the dashboard.
Checkout and return URLs
status=paid is in the address, but the API says pending
Believe the API. The address can be opened by anybody, with any parameters. See Hosted checkout and return URLs.
status=failed is in the address, but the API says pending
The payer's attempt did not go through, but the payment is not over: they can press Try again and pay until expires_at. Do not cancel the order because of the address. See Hosted checkout and return URLs.
The payer was not sent back to my site
The result page never sends the payer back by itself: it stays on screen so they can read the payment's details, and a Return to button opens your success_url or cancel_url. The button is missing when the payment has no success_url or cancel_url and your store has no default, or when the address does not pass the rules for the environment (for example http:// on a live payment), in which case Raïs does not use it.
The checkout does not show in my iframe
It cannot be embedded: it forbids being framed, to protect payers. Redirect to it or open it in a browser tab.
The deep link does not open my app
Check that the scheme in success_url matches the one your app registers, that the payer's browser session is allowed to open it (use ASWebAuthenticationSession or Custom Tabs, not a plain web view), and that you did not add a space or a non-ASCII character to the address.
Refunds and receipts
payment_not_refundable
Only paid and partially_refunded payments can be refunded.
My refund is pending
The gateway's answer was lost, and the amount is reserved until the refund is settled by hand. Do not repeat it with a new key, and contact support with the refund id. See Refunds.
The receipt answers 404
Only paid, partially refunded and refunded payments have a receipt, and a key only sees receipts of its own store and environment. See Receipts.
insufficient_balance
A live refund is taken out of your store's balance together with its fee (10.00 DA), so the balance must cover both: "The store's balance is not sufficient to cover this refund and its fee." Top up your balance, wait for payments to come in, or refund a smaller amount. See Refunds.
Are the fees returned when I refund a payment?
No. The fee of a payment is not returned when it is refunded, in full or in part, and the refund has its own fee of 10.00 DA. See Pricing and fees.
Going live
My live key answers 401 invalid_api_key
The key is unknown, revoked or expired, it is used from an address outside its allowlist, or its store is not approved for live payments (any more). The business has to be verified before a live key can exist: see Going live. The API never says which of these it is.
I used a live key with a sandbox payment id
Sandbox and live are separate. The payment is not found (404) because it belongs to the other environment.
Is Raïs PCI DSS or SOC 2 certified?
Raïs does not claim either certification. What the documentation states is what the platform does: the card is typed on a page hosted by Raïs or SATIM and never reaches your servers, keys are stored hashed, saved cards are encrypted. See Security and safety.
Quick reference
| You see | Probably | Do |
|---|---|---|
401 invalid_api_key | A wrong, revoked or expired key, a call from an address outside the allowlist, or a store that is suspended or not approved for live payments | Check the key, its environment and the store |
403 insufficient_scope | The key lacks the scope | Create a key with the scope |
400 invalid_amount | A float or a string | Send an integer in centimes |
400 amount_too_small | Less than 50.00 DA | Raise the amount |
400 invalid_url | A bad return URL for the environment | Use https, or your app scheme |
404 payment_not_found | Another store or environment, or a bad id | Use the key of the right store and environment |
409 payment_not_cancellable | The payment is not pending | Refund it instead if it is paid |
400 insufficient_balance | A live refund and its fee are more than your balance | Top up your balance, or refund less |
422 idempotency_key_reused | The same key with different parameters | Use a new key for a new request |
429 rate_limit_exceeded | Too many requests, or too many failed authentications from this address | Wait for Retry-After, and stop using revoked keys |
503 gateway_unavailable | The gateway cannot be reached | Retry shortly with the same key |
Still stuck? Write to salam@rais.app with the Rais-Request-Id of the request that failed.