Integration security
Treat API keys + webhook secrets as production credentials. Verify every webhook signature you receive. Prefer narrow scopes. Rotate keys you no longer trust.
API keys
Keys are formatted ak_live_<32 base32 chars> for production, or ak_test_… for the sandbox. We persist a SHA-256 hash plus the first 8 + last 4 characters — the full secret is shown once at mint time and is unrecoverable afterwards.
Storage
Never commit keys to version control. Use environment variables, a secret manager (Vault, AWS Secrets Manager, Doppler, 1Password Connect), or your platform's deployment-time secret store (e.g. Vercel env vars).
Least scope
Mint a key with the smallest scope (READ, WRITE, or ADMIN) that the integration needs. Webhook subscription CRUD requires ADMIN — a service that only sends SMS does not.
Suspected leak
Revoke immediately at /portal/api-keys. Revocation propagates on the next cache flip — within a minute every request using that key returns 401 unauthorized. Mint a replacement before you revoke if you need zero downtime.
# Environment variable setup (recommended)
export ADAPT_API_KEY="ak_live_..."
# Don't do this — keys in source ship to every reader of the repo
const apiKey = "ak_live_..."Verifying webhook signatures
Every webhook delivery carries a X-AdaptLive-Signature header in this shape:
X-AdaptLive-Signature: t=1717009200,v1=8a3e…<64 hex chars>t— the unix-second timestamp at the moment we signed.v1— HMAC-SHA256(${t}.${rawBody}) using the subscription'swhsec_…signing secret, hex encoded.
Reconstruct the same payload (`${t}.${rawBody}`) on your side, recompute the HMAC, and compare with crypto.timingSafeEqual(or your language's constant-time equivalent).
Replay protection. Reject anything where |now - t| > 300s.
// Node.js — copy/paste verifier
import crypto from "node:crypto";
export function verifyAdaptLiveWebhook(args: {
signingSecret: string; // 'whsec_...' from POST /v1/webhooks
rawBody: string; // the exact bytes you received (don't re-stringify)
signatureHeader: string; // value of 'X-AdaptLive-Signature'
toleranceSeconds?: number;
}): { ok: true } | { ok: false; reason: string } {
const parts = args.signatureHeader.split(",").map((s) => s.trim());
const tEntry = parts.find((p) => p.startsWith("t="));
const v1Entry = parts.find((p) => p.startsWith("v1="));
if (!tEntry || !v1Entry) return { ok: false, reason: "Malformed header" };
const t = Number(tEntry.slice(2));
const v1 = v1Entry.slice(3);
const now = Math.floor(Date.now() / 1000);
const tolerance = args.toleranceSeconds ?? 300;
if (!Number.isFinite(t) || Math.abs(now - t) > tolerance) {
return { ok: false, reason: "Timestamp outside tolerance window" };
}
const expected = crypto
.createHmac("sha256", args.signingSecret)
.update(`${t}.${args.rawBody}`)
.digest("hex");
const a = Buffer.from(expected, "hex");
const b = Buffer.from(v1, "hex");
const ok = a.length === b.length && crypto.timingSafeEqual(a, b);
return ok ? { ok: true } : { ok: false, reason: "Signature mismatch" };
}Python, Ruby, and Go verifier snippets live on the Webhooks reference page.
HTTPS only
Subscription URLs must use HTTPS. Plain HTTP is rejected at create time.
Idempotent handlers
Deliveries are retried on transport error. Dedupe on the eventId in the body so a retry never double-processes the same event.
Receiver hardening
Use the raw body for verification
JSON re-stringification reorders keys and changes whitespace, which breaks the HMAC. Wire your framework's raw-body middleware (express.raw(), await req.text() in App Router before JSON parsing) and verify against those exact bytes.
Respond fast, defer work
Verify, ack with 2xx, then push the payload to a background queue. Anything >10s gets retried as if it failed.
TLS 1.2 or higher on the receiver
Use HTTPS endpoints with TLS 1.2 or higher. If you self-terminate TLS, configure your reverse proxy to disable legacy versions.
Security checklist
- ✓Store API keys in environment variables or a secret manager — never source control.
- ✓Use the smallest API key scope the integration needs.
- ✓Verify every webhook signature with constant-time comparison.
- ✓Reject deliveries whose timestamp is more than 5 minutes off.
- ✓Verify against the raw request body, not a re-serialized copy.
- ✓Dedupe on
eventIdso retries are safe. - ✓Revoke + rotate the moment a key looks compromised.
For platform-level compliance posture (SOC 2, GDPR, CCPA, TCPA, HIPAA-aware) see the Trust Center.
