Security and help
Security and safety
How to handle keys, verify webhooks, protect personal data, and what Raïs does with card data.
A payment integration holds three kinds of secrets: your API keys, your webhook signing secrets and your customers' data. This page says what Raïs does about each of them, and what is left to you.
Card data never touches your servers
The payer types their card on a page hosted by Raïs (or by SATIM, with payment_page: satim), over HTTPS. The card number, expiry and security code go from the payer's browser to that page and on to SATIM. They never pass through your website, your server or your mobile app, so you never have to receive, store, transmit or protect them. What your systems receive is a payment object with the brand, the last four digits and a masked number.
Every card payment is authenticated with 3-D Secure by SATIM: the payer confirms with a one-time password from their bank, which proves that they hold the card.
The hosted checkout cannot be embedded in another page (it forbids being framed), restricts what the browser may load with a strict content security policy, and is served with no-store caching.
What Raïs does
| Area | What Raïs does |
|---|---|
| Transport | The API and the hosted checkout are served over HTTPS. API responses carry a Strict-Transport-Security header, Cache-Control: no-store, X-Content-Type-Options: nosniff and a content security policy that forbids loading anything. |
| API keys | A key has 256 bits of randomness and is shown once. Raïs stores only its SHA-256 hash, so it cannot show it again and a database leak does not reveal it. Keys have scopes and an optional IP allowlist, have no expiry date, and can be rolled and revoked. |
| Isolation | A key belongs to one store and one environment. It cannot read, cancel, capture or refund the payments, refunds and events of another store or of the other environment: the answer is 404, as if they did not exist. |
| Webhooks | Every webhook of the current format is signed with HMAC-SHA256 over a timestamp and the raw body, and is never sent unsigned. Raïs checks the target address before every attempt, refuses loopback, private and internal addresses, never follows a redirect, and switches off an endpoint that keeps failing. |
| Rate limiting | Each key is limited to 60 requests a second (bursts of 120), and an address that sends 30 failed authentications in a minute is throttled. Every failure of authentication gets the same answer, so a key cannot be guessed from the errors. |
| Retries | Idempotency keys make a repeated request safe: nothing is created or refunded twice. |
| Stored cards | Cards kept for a customer (see below) and the card details of a payment in progress are stored encrypted with AES-256-GCM. Every value has its own data key, wrapped by a master key that is kept outside the database, so a copy of the database alone is of no use. |
| Your account | Dashboard accounts can turn on two-factor authentication with an authenticator app, and come with backup codes. Team members have roles: only owners, admins and developers manage API keys and webhooks, and only owners and admins refund payments and change store settings. |
| Audit log | Sensitive actions are written to an append-only audit log with who did them and when: creating, rolling and revoking keys, changing webhook endpoints, rolling a signing secret, refunds from the dashboard, store changes. Secrets are never written to it. |
| Verification | Live payments need a verified business: Raïs reviews the company's documents and the identity of its owners before a store can take live payments. |
| Logs | Raïs logs each API request once, with its request id, method, path, status, duration, key prefix and client address. It never logs headers, query strings or bodies, which is where secrets and personal data would be. |
Raïs does not claim a PCI DSS or SOC 2 certification, and nothing in these pages should be read as one.
API keys
- Keep secret keys on your server, in a secret manager or environment variables. Never in front-end code, mobile apps, repositories, logs or support tickets.
- The API sends no CORS headers: browsers cannot use a key, by design.
- Give each service its own key, with the fewest scopes it needs, and an IP allowlist when your servers have fixed addresses.
- Roll keys on a schedule and when people who knew them leave. Rolling with a grace period needs no downtime.
- If a key may have leaked: roll or revoke it first, then review recent payments and refunds in the dashboard, and contact salam@rais.app with the request ids you care about.
See Authentication.
Webhooks
- Verify every signature on the raw body, in constant time, before you read anything else. Reject a request without a valid
Rais-Signature. - Check the timestamp. A tolerance of 300 seconds stops an attacker from replaying a captured request later. Keep your server's clock accurate (NTP).
- De-duplicate on the event
id, because delivery is at least once. A replay inside the tolerance window then does nothing. - Use https. A live endpoint must use it; sandbox endpoints should too.
- Do not rely on the sender's IP address. The signature is the proof.
- Rotate the signing secret when it may have been exposed. During the 24 hours that follow, both secrets sign, so nothing is missed.
- The legacy format, kept for stores that configured a webhook URL before API keys existed, is signed only when the store has a secret: set one, and verify it.
See Webhooks.
Threats and defences
| Threat | Defence |
|---|---|
| A stolen key | Scopes, IP allowlist, rolling and revocation; keys are stored only as a one-way hash at Raïs. |
| A forged webhook | The HMAC signature, which only Raïs and you can compute. |
| A replayed webhook | The signed timestamp with a 5 minute tolerance, and de-duplication on the event id. |
| A duplicated request | The Idempotency-Key header. |
| A forged return redirect | Never trust the address. Confirm with the API or the webhook. |
| A tampered amount | Compute amounts on your server only; never accept an amount from a browser or an app. |
| Clickjacking on the checkout | The checkout refuses to be framed. |
| Key guessing | Throttling of repeated failed authentications (30 a minute per address) and one generic answer for every kind of failure. |
| A payer's data in the wrong hands | Send only the personal data you need, and treat what you receive like any customer data. |
TLS
The API and the hosted checkout are served over HTTPS. Call https://api.rais.app for the API, send the payer to the https://pay.rais.app address of the checkout_url, keep certificate verification on in your HTTP client, and never use http:// for live traffic.
Personal data
Payments carry personal data: the payer's name, e-mail and phone number when you send them, and the card details SATIM returns.
What Raïs stores about a payment: its amount, currency, status, description, reference and metadata; the payer details you provided; the card brand, the masked card number (never more than the first six and last four digits), the cardholder name and the approval code that SATIM reports; the SATIM order id; its receipt as a file; and the events about it, which the API serves for 30 days.
What Raïs does not give you: a full card number, an expiry date or a security code. They are not in the API, in events or in receipts.
Saved cards
A payer can choose to keep a card for next time. It is always their choice, and it works on the Raïs payment page only: the SATIM page collects the card itself, so nothing can be saved there. This is how it works:
- Create the payment with the
customer_idof one of your customers: that names whose cards they are. A payment without it never offers to save a card. - On the Raïs payment page the payer then sees a box to save the card for next time. It is not ticked: nothing is saved unless they tick it themselves.
- The card is kept, encrypted, only once the payment is paid, for that customer and for your store only. Its security code is kept with it, sealed in its own field, so the payer does not have to type it again. The box says so in plain words before they tick it.
- On a later payment with the same
customer_id, the checkout lists their saved cards as masked numbers. The payer picks one and confirms with 3-D Secure, every time, like for any other card payment. - A saved card can only pay for its own customer, and only in the store and the mode that saved it: a card saved on a sandbox payment is never offered on a live payment, nor a live card on a sandbox one. No API call returns it or charges it. In your dashboard you see a card's brand, last four digits and expiry, and you can remove it: it is then erased for good. Deleting a customer erases their cards too.
- Customers come from the dashboard (Customers: add one by hand, or find the ones Raïs made), or from your payments: when a payment that carries
customer.nameorcustomer.emailis paid, Raïs makes a customer record from it and returns its id ascustomer.id. Pass that id ascustomer_idon the next payment.
Say in your own terms that payers may choose to keep a card for faster payments.
Personal data: your part
- Send only the personal data you need. A name and an e-mail address are enough for a receipt.
- Never put card numbers, passwords, tokens or other secrets in
description,referenceormetadata: the description is sent to SATIM and printed on the receipt, and the reference is added to the address the payer returns to. - Treat payer data in your database, logs and exports with the same care as any customer data, and delete it when you no longer need it.
The Privacy policy describes how Raïs handles personal data.
Safe integration checklist
- Secret keys only on servers, one per service, scopes minimal.
- Amounts computed on the server.
- Idempotency keys on every
POSTthat creates something. - Webhook signatures verified on the raw body; events processed once.
- Payments confirmed from the API or a webhook, never from a redirect.
- https for every live address.
- Request ids logged, secrets never logged.
- Keys and secrets rolled on a schedule.
- Two-factor authentication on every dashboard account that can manage keys or refund.
Reporting a problem
If you find a vulnerability, write to salam@rais.app with a description and steps to reproduce it. Please do not access data that is not yours, and do not test against real payers.