Adapter protocol
The three signed endpoints your user database implements, with request and response formats and the signature scheme.
The adapter protocol (version 2) is how 9in talks to your own user database. You implement three endpoints under a base URL; 9in calls them with signed JSON POST requests and expects signed responses. JSON Schemas: user, /authenticate request, /user request, event.
#Requests
| Header | |
|---|---|
Content-Type | application/json |
9in-signature | t=<unix seconds>,v1=<hex HMAC-SHA256> |
9in-request-id | A unique id for this request |
The signature covers <t>.POST.<path>.<raw body> with the shared secret from the console. Reject requests more than 5 minutes off, or with a wrong signature (compare in constant time).
#Responses
Sign every response with your own 9in-signature header, computed the same way over <t>.POST.<path>#<request id>.<response body>, so a response can't be reused for another request. 9in rejects unsigned or wrongly signed responses. 9in waits up to the configured timeout (3 seconds by default).
#POST /authenticate
Check a password.
{ "identifier": { "type": "email", "value": "jane@acme.com" }, "password": "…" }type is email, username or phone.
| Status | Body |
|---|---|
| 200 | { "user": <user> } |
| 401 | { "error": "invalid_credentials" } (wrong password or unknown user: don't tell them apart) |
| 403 | { "error": "blocked" } |
#POST /user
Look a user up.
{ "id": "42" }or
{ "identifier": { "type": "phone", "value": "+447700900123" } }| Status | Body |
|---|---|
| 200 | { "user": <user> } |
| 404 | { "error": "not_found" } |
#POST /event
{ "id": "evt_…", "type": "otp.requested", "createdAt": "2026-11-09T08:00:00.000Z", "data": { … } }Answer any 2xx (204 is fine).
| Type | data |
|---|---|
otp.requested | userId, channel (email or sms), to, code, purpose (login or password_reset), expiresAt (ISO 8601) |
user.updated | userId, fields |
user.password_changed | userId, fields |
userId is your id. Passwords are never sent.
#The user object
| Field | Type | |
|---|---|---|
id | string, required | Your user id (up to 256 characters) |
email | string or null | |
email_verified | boolean or null | |
phone_number | string or null | E.164 |
phone_number_verified | boolean or null | |
username | string or null | |
name, given_name, family_name | string or null | |
picture | string or null | https URL |
locale, zoneinfo | string or null | |
birthdate | string or null | YYYY-MM-DD |
blocked | boolean or null | true refuses sign-in |
metadata | object or null | Stored on the 9in user |
Unknown fields are ignored.