Corteksa
GuidesIntegrations

Best Practices

Integrations — Best Practices

The recommended way to build a safe, resilient integration.

✓ Treat credentials as secrets

An API key (crtk_live_…), OAuth access/refresh token, and client_secret are all passwords. Store them encrypted, server-side, per customer — never in a browser, an app bundle, or logs. If you need a reference in logs, log only the non-secret token_prefix (crtk_live_a1b2c3d4), never the full value.

✓ Request least-privilege scopes

Ask for only the {verb}.{objectSlug} scopes you actually use — read.contacts before create.contacts, and neither if you only read one object. The customer sees exactly what you request on the consent screen, and a key can only ever be granted scopes its creator holds. Verify what you got via GET /me and warn early if a scope you need is missing.

✓ Prefer OAuth for published, multi-customer apps

A pasted API key is perfect for a self-serve, single-workspace connection. For a listed integration serving many customers, use the OAuth "Connect" flow: tokens are revocable (a customer disconnecting kills live tokens at once, not at expiry) and short-lived, which limits blast radius if one leaks.

✓ Rotate the refresh token every time

The refresh_token grant rotates — each refresh returns a new refresh_token and invalidates the old one. Always persist the new value. Re-presenting an already-rotated refresh token is treated as a leak and revokes the entire token family, so a stale copy will lock you out.

✓ Handle 401 as "disconnected"

A revoked key or grant, or a lapsed expiry, returns 401 on the next call. Clear the stored credential and prompt the customer to reconnect — don't retry a dead credential in a loop.

✓ Verify every webhook signature

Recompute X-Corteksa-Signature (t=<unix>,v1=<hex> = HMAC-SHA256(signing_secret, "<t>.<raw-body>")) over the raw request body, compare in constant time, and reject timestamps older than ~5 minutes to stop replays. Never trust a payload you haven't verified.

✓ Make webhook handlers idempotent

Deliveries retry on any non-2xx or timeout. Dedupe on the X-Corteksa-Delivery header so a retried delivery doesn't double-apply. Respond 2xx quickly and do heavy work asynchronously.

✓ Respect the rate limit

API-key traffic is capped per key (API_KEY_RATE_LIMIT_PER_MINUTE, default 120/min). Batch where you can, and back off on 429 rather than hammering.

✗ Don't put the token flow in the browser

Never call POST /oauth/token, hold a client_secret, or handle a crtk_oauth_/crtk_refresh_ token in client-side code. The browser's only job in "Connect" is the consent screen, which yields a short-lived code delivered to your backend. Public (SPA/mobile) clients must use PKCE instead of a secret.

✗ Don't over-grant wildcards

{verb}.* scopes are convenient for a sync-anything mapping UI, but they expand to every object the owner can reach. Prefer explicit {verb}.{object} scopes unless the app genuinely needs breadth.

✗ Don't re-serialize the webhook body before verifying

Signatures are computed over the exact bytes sent. Parse the JSON after you've verified the signature over the raw body — re-encoding changes the bytes and breaks the HMAC.

✗ Don't rely on scopes for row-level security

Scopes gate which endpoints a credential can reach, not which rows. Data is still filtered per record by the acting admin's A/G/M/D level — never assume a read.contacts scope means "all contacts". See Backend Integration.

Next

On this page