Security Best Practices for Integrators
A verification protects you only if your integration cannot be tricked into accepting a result that is not real, not yours or not for this transaction. These practices apply to every product.
1. Publishable keys and secrets
| Credential | Where it may live |
|---|---|
Client key vhk_live_… |
Browser widgets, mobile apps, request headers |
Client secret vhsk_live_… |
Your server's secret store only |
Webhook signing secret whsec_… |
Your server's secret store only |
- Never put a client secret or webhook secret in browser code, a mobile app bundle, a URL or version control.
- Send keys in headers, not in URLs. Where an endpoint only reads the
clientKeyparameter, send it in the request body or call it only from your server. - Redact
vhk_,vhsk_andwhsec_values from your logs. - Use separate keys per integration and per environment.
2. Confirm every result on your server
The browser is under the visitor's control. Never grant access because of:
- a widget
onSuccesscallback; - a redirect to your success URL;
- the public
GET /hosted/status/<attempt-token>; - a flag the widget stored in the visitor's browser.
Instead confirm on your server with one of:
| Method | Proves |
|---|---|
| Signed webhook | Event from VerifyHuman for your account, with environment, product and status |
| Evidence Receipt | Signed result of a verification made with your key |
POST /api/validate (client secret) |
A VerifyHuman widget token issued for your account |
POST /api/v1/verify-age/validate (client secret) |
A VerifyAge pass of your account meeting your minimum age |
3. Check product, environment and policy
For every result you accept, check:
- the product is the one you asked for (an identity check is not an age check);
- the environment is
live(Test results never unlock Live access); - the status is
PASS; - for VerifyAge, the enforced minimum age is at least your requirement and the requirement was met.
Store VerifyAge minimum ages on the server (Wizard, hosted page or VerifyFlow), never in browser code. See VerifyAge.
4. Prevent replay and substitution
- Bind the attempt to your session. Start hosted attempts from your server
with the Hosted Attempts API and require the
returned
attemptto match the one you stored. - Use each result once. Record every
verification_idyou accept and refuse it the second time. - Check freshness. Reject results that completed too long ago for your use.
- Treat a
404as "not yours". Owner-scoped endpoints return404for another account's verification.
5. Signed webhooks
- Verify
X-VerifyHuman-Signature(HMAC-SHA256 over{timestamp}.{body}) with a constant-time comparison. - Reject deliveries whose
X-VerifyHuman-Timestampis older than about 5 minutes. - Process each event
idonce; retries reuse it. - Rotating a signing secret takes effect immediately: update your verifier at the same time.
See Webhooks.
6. Evidence Receipts
- Verify the RS256 signature with the key from
/.well-known/jwks.jsonthat matchessignature.kid; refetch the JWKS when you meet an unknownkid. - Reject receipts with
alg: noneor an unsupportedreceipt_version.
7. Safe redirects
- Use HTTPS redirect and return URLs. Custom URL schemes are not accepted; use universal links / App Links for mobile apps.
- Register your return domains on the API key used with the Attempts API.
- Treat query parameters on your return URL as untrusted input.
8. Rotating API keys
Create a new key, deploy it, confirm it works, then deactivate the old key. Rotate immediately if a secret may have been exposed. See API Keys.
9. Browser integration limits
- Restrict browser keys with allowed domains.
- A browser widget can be inspected and modified by the visitor; everything it reports is a claim until your server confirms it.
- The VerifyAge widget's optional state detection uses the visitor's IP address through a third-party geolocation service; disclose it if you use it.
10. Test and Live separation
Keep Test and Live credentials, pages and webhook endpoints separate, and make your production configuration fail closed if a Test key is deployed. See Test and Live Environments.