---
url: /security.md
---
# Security

Keep every secret on your server. The requirements below protect payment and customer data.

## Card data never touches you

Don't collect card details on behalf of the checkout. Customers enter them in the **Maytes-hosted checkout window**, and our payment provider processes them. Card numbers never pass through your servers, frontend, or the Maytes merchant API, so integrating Maytes doesn't put card data in your PCI scope. There is no API for submitting card details.

## Your three secrets

| Secret | Used for | If it leaks |
|---|---|---|
| `client_secret` (per environment) | Minting OAuth access tokens | An attacker can act as your merchant account: create, capture, cancel, and refund. |
| Webhook **signing secret** (per subscription) | Verifying that deliveries came from Maytes | An attacker can forge "authorized" events at your endpoint. |
| OAuth **access tokens** | Every merchant API call | Same power as the credentials, until expiry. |

Rules for all three:

* **Server-side only.** Never put them in browser or mobile code, a repository, or client-accessible configuration. The [Checkout Button](/checkout-button) calls your backend, which calls Maytes.
* **Store them in a secret manager** or environment variables injected at deploy time.
* **Separate them by environment.** Test and production use independent credentials and webhook secrets, so a test leak doesn't expose production.
* **Don't log them.** Redact `authorization` headers and token responses from request logging.

## Verify every webhook delivery

Verify the **`X-Maytes-Signature`** header on every webhook using the raw body, constant-time comparison, every `v1` entry, and a timestamp tolerance (we recommend 5 minutes). Reject failures with a 4xx and don't process the payload. These checks prevent forged or replayed requests to your guessable endpoint URL. Use the SDK examples in [Webhooks → Verify the signature](/webhooks#verify-the-signature).

## Rotate secrets without downtime

* **Webhook signing secret:** follow the [rotation runbook](/webhooks#rotating-your-signing-secret). The built-in 2-hour dual-signature grace window supports scheduled or staff-departure rotations without dropping deliveries. For a suspected leak, set `revoke_previous=true` to revoke the old secret immediately.
* **OAuth client secret:** rotate it from the portal's **API credentials** page. Rotation is immediate, with no grace window, so deploy the new value at once. See [Authentication](/authentication#rotating-your-client-secret).

## Trust server-side state, not the browser

Before fulfilling an order, verify server-side state. Browser state, including arrival at your `return_url`, can be forged.

* Fulfill on your own **capture success** (a call your server made with its own credentials) — never on the redirect.
* Use the capture assertions (`expected_total_amount`, `currency`, `merchant_order_id`) to prevent a mixed-up ID from capturing the wrong order.

## Handle customer data minimally

Send the optional `customer_data` block on [create](/create-checkout) only when you have and need the customer's name, email, or mobile. Don't invent placeholder values. Treat this information as personal data under your privacy obligations. Maytes encrypts it at rest and uses it to pre-fill the customer's checkout experience.

## Transport

Use HTTPS for your webhook endpoint. All Maytes endpoints are also HTTPS-only. Webhook signatures provide authenticity and integrity; TLS provides confidentiality in transit.

## What's next

* [Webhooks](/webhooks) — signature verification and secret rotation in full.
* [Authentication](/authentication) — the OAuth token lifecycle.
* [Sandbox environment](/sandbox-environment) — including the signature-rejection and duplicate-delivery checks in the go-live list.
