# Security practices

This page collects the practices that keep your integration and your customers' data safe: key handling, least privilege, network restrictions, webhook verification and data handling. A live key can place real calls to real people and spend your workspace's balance, so treat it like a payment credential.

## Keys

- **Server-side only.** Never put an API key in a web page, mobile app, desktop app, browser extension or anything else you ship. The API refuses live keys sent from browsers ([`live_key_not_allowed_in_browser`](/errors/live_key_not_allowed_in_browser)), but a key in shipped code is exposed whether or not the API accepts it from there.
- **Least privilege.** Give each key only the scopes its system needs (see [API keys](/getting-started/api-keys#scopes)). A reporting job does not need `calls:write`; a contact sync does not need `transcripts:read`.
- **One key per system and environment.** Then you can rotate or revoke one without touching the others, and the console's **Last used** and request counts tell you which system did what.
- **Always an expiry.** Live keys always expire; give test keys an expiry too unless they are only on your own machine. Track `api_key.expires_at` from `GET /v1/workspace` and rotate before it.
- **Store in a secret manager** or deployment-provided environment variables. Never in source control, container images, logs, crash reports, analytics, URLs or tickets.
- **Never log credentials.** Redact the `Authorization` header in your HTTP client's logging and in proxies. Log the key id (`key_...`) and the request id instead.
- **Keys follow people.** A key stops working when its creator leaves the workspace, and loses scopes when the creator's role is reduced. Create production keys from an account that will stay, and replace keys before someone leaves.

## If a key is exposed

1. In the console, **rotate** the key with the grace period **Immediately**, or **revoke** it. Either takes effect at once.
2. Deploy the replacement key.
3. Review what the key did: its **Last used** time and address in the console, and the calls, campaigns and contacts changed in that mode around that time.
4. If live calls may have been placed by someone else, write to [support@relationsai.com](mailto:support@relationsai.com) with the key id (never the key itself) and the time window.

## Restrict where keys work

If your servers have fixed outbound addresses, ask a workspace admin to set an **IP allowlist** in the console security settings. API requests from other addresses are then refused with [`ip_not_allowed`](/errors/ip_not_allowed), so a leaked key is useless elsewhere. If the allowlist cannot be checked, the API refuses the request rather than letting it through.

## Test before live

- Develop, test and run CI with test keys. Test mode never dials anyone or charges anything.
- Keep live keys out of development machines, CI and staging.
- At startup, check `livemode` from `GET /v1/workspace` against the environment you expect, and refuse to run if they differ.
- Use the fictional test numbers (`+12025550100` to `+12025550106`) in tests and fixtures, never real people's numbers.

## Webhooks

- **Verify every signature** with the endpoint's secret, over the raw body, in constant time, and reject events whose `t` is more than 5 minutes off. See [Verifying webhook signatures](/guides/verifying-webhook-signatures).
- **Deduplicate** by event id; deliveries are at least once.
- **Check `livemode`** and only act on events of the mode the endpoint is meant for.
- **Treat the payload as a notification.** For actions that matter (billing, follow-ups), read the object from the API before acting.
- **Keep the secret secret**: store it like an API key, and rotate it if it may have been exposed: with `grace_period_hours: 0` the old secret stops signing at once. A replayed create or rotate response shows `secret: null`, so the first response is the only copy; if you lose it, rotate again.
- **Answer fast and process later**, so slow work cannot cause timeouts and redelivery storms.

## Call data

Transcripts, recordings, analysis and contact data contain personal data about the people your agents call. The API returns them in full to your workspace, so protecting them in your systems is your responsibility.

- **Restrict access** to the systems and people that need it, and only grant `transcripts:read` and `recordings:read` to keys that need them.
- **Recording links are credentials.** The `url` from [`GET /calls/{call_id}/recording`](/api/recordings#retrieve-a-call-recording-link) works without an API key until it expires (about 15 minutes). Do not log, store or forward it; ask for a fresh link when you need one. An expired link answers [`media_link_expired`](/errors/media_link_expired). It also stops working (`404`) as soon as the API key that obtained it is revoked or expires, loses `recordings:read`, or its creator leaves the workspace. Revoking a leaked key therefore also cuts off the recording links it obtained.
- **Keep data only as long as you need it**, and delete it from your systems according to your retention policy.
- **No secrets or sensitive data in `metadata`.** It is stored and returned as sent; use your own ids instead.
- **Keep consent and the do-not-call list accurate.** Record consent only when the person really gave it, record withdrawals promptly, and add do-not-call requests as soon as you receive them. See [Consent and do-not-call](/guides/consent-and-dnc).

## Your own endpoints and logs

- Serve your webhook endpoint over HTTPS with a valid certificate.
- Do not echo request bodies or headers into error pages.
- When you write to support, follow [Support](/resources/support): never send keys, full `Authorization` headers, webhook secrets, customer data or transcripts.

## Reporting a problem

Report a suspected security issue in the API or these docs to [support@relationsai.com](mailto:support@relationsai.com) with "Security" in the subject. Describe what you found and how to reproduce it, without including real customer data or live credentials.
