stile
Guides

Security Best Practices

Protect your integration with webhook verification, key management, and anti-fraud measures.

Webhooks are the source of truth

The client-side stile:verified event is a convenience signal for updating your UI. It can be spoofed by anyone with browser dev tools. Never grant access, unlock content, or fulfill orders based on client events alone.

Never trust the client

Always wait for the signed verification_session.verified webhook before granting access. The webhook is sent server-to-server over HTTPS and includes an HMAC-SHA256 signature that proves it came from Stile.

Webhook signature verification

Every webhook delivery includes a stile-signature header. Always verify it before processing the event. An unverified webhook endpoint is equivalent to no verification at all — anyone can POST a fake payload to your URL.

See the Webhook Signature Verification guide for the raw algorithm and copy-paste handlers in Node.js, Python, Go, Ruby, and PHP.

Key management

Stile uses two types of API keys:

Key typePrefixWhere to use
Secret keyvk_test_ / vk_live_Server-side only. Creates sessions, reads results, manages webhooks.
Publishable keypk_test_ / pk_live_Safe for frontend code. Opens the verification widget. Cannot read results or manage resources.

Never expose secret keys

Secret keys (vk_) must never appear in frontend code, client-side bundles, mobile apps, or public repositories. If a secret key is compromised, rotate it immediately in the dashboard.

Key rotation — You can rotate keys in the dashboard without downtime. Both the old and new key remain valid during a configurable grace period, giving you time to update your servers.

Rate limiting

API requests are rate-limited per key:

ModeLimit
Test (vk_test_ / pk_test_)100 requests per minute
Live (vk_live_ / pk_live_)1,000 requests per minute

When you exceed the limit, the API returns 429 Too Many Requests with a Retry-After header indicating how many seconds to wait before retrying.

if (res.status === 429) {
  const retryAfter = parseInt(res.headers.get("Retry-After") || "1", 10);
  await new Promise((r) => setTimeout(r, retryAfter * 1000));
  // retry the request
}

See Error Handling for full retry-with-backoff examples in multiple languages.

PII handling

Stile is designed to minimize your PII exposure:

  • Raw identity data is purged after verification — images, full names, dates of birth, and document numbers are not stored long-term.
  • Only hashed anchors persist — email hashes, phone hashes, and document fingerprints are retained for deduplication and returning-user lookup.
  • Verification outcomes persist — the age tier result (e.g. over_21), credential method, and expiry are stored so returning users can skip re-verification.

This means you never need to store or handle raw identity documents yourself. The verification result tells you what you need to know (age tier, pass/fail) without exposing the underlying PII.

Anti-spoofing measures

Stile employs multiple layers to prevent identity fraud:

MeasureWhat it catches
Barcode cross-referencePrinted photos of IDs — the barcode data must match the visual fields.
Head-turn liveness challengeFlat images and photos held up to the camera — the user must turn their head to prove they are physically present.
Server-side frame analysisSDK spoofing and injected video feeds — frames are analyzed server-side, not just on the client.
Face matchStolen or borrowed IDs — the selfie must match the photo on the document.
Document expiry checkExpired documents are rejected.

VPN and geolocation detection

If your use case involves physical delivery (e.g. alcohol, cannabis), pass the delivery_jurisdiction parameter when creating a session:

curl -X POST https://api.stile.dev/v1/verification_sessions \
  -H "Authorization: Bearer vk_live_..." \
  -H "Content-Type: application/json" \
  -d '{
    "type": "age",
    "use_case": "alcohol_delivery",
    "delivery_jurisdiction": "US-CA"
  }'

Stile compares the user's IP-based geolocation against the delivery jurisdiction. If there is a significant mismatch (e.g. the user's IP is in a different country), the session is flagged for review.

VPN detection is advisory

A geolocation mismatch does not automatically fail the session. It adds a flag to the verification result that your backend can use to apply additional checks or manual review.

Email OTP as ownership proof

When a returning user is looked up by email, they must complete an OTP challenge to prove they control that email address. This is an important distinction:

  • OTP proves: "This person controls this email address."
  • OTP does not prove: Age, identity, or any other verification claim.

The actual proof of age or identity comes from the underlying verification credential that was previously issued. The OTP simply gates access to that credential so a different person can't reuse it.

See Returning Users for the full reverification flow.

On this page