Submit and retrieve device fingerprints
Integrate event submission on your server-side. Your app collects a fingerprint payload with the Stitch SDK. Following this, your server-side submits it to the Stitch API and optionally re-reads events or device signals.
API base: https://api.stitch.money/v2/
Token endpoint: https://secure.stitch.money/connect/token
Test vs live is determined by clientId, not a different base URL.
Client token
Event submission and reads are protected by a client token. Follow the steps described in the client token guide to obtain a client token with the client_fingerprinting scope.
POST https://secure.stitch.money/connect/token
Content-Type: application/x-www-form-urlencoded
grant_type=client_credentials
&client_id={client_id}
&client_secret={client_secret}
&scope=client_fingerprinting
Use the access token only on your servers. Never embed the client secret or access token in a mobile or web app.
Submit an event
POST https://api.stitch.money/v2/fingerprint/events
Authorization: Bearer {access_token}
Content-Type: application/json
{
"correlationId": "login-123",
"userId": "user-abc",
"fingerprint": "base64-sdk-payload",
"metadata": {
"action": "login",
"channel": "my-app-web"
}
}
| Field | Required | Notes |
|---|---|---|
fingerprint | Yes | Exact SDK output string. |
correlationId | No | Optional business reference for your logs and support (for example your login attempt or order reference). Max length 128 characters. Useful if a response is lost and you never received the event id. If omitted, Stitch assigns one and returns it. Not an idempotency key and not a nonce: the same value on a later POST still creates a new event, and GET ?correlationId= may return more than one. |
userId | No | Your user identifier (merchant-supplied). Send when known (for example after login). Omit for anonymous or pre-auth events. Stored and returned; never used for device matching. |
metadata | No | Freeform customer context stored with the event. Use a flat JSON object. Suggested keys include action and channel. See Metadata. |
Taken from the OAuth access token. clientId is not a request field. Do not send it in the body.
Use top-level correlationId for anything you may need to look up later, as this provides your support and lookup handle for a submit (GET /v2/fingerprint/events?correlationId=). Putting the same value only in metadata stores it, but Stitch will not query events by metadata keys.
Safe retries of the same HTTP request use the standard Stitch Gateway Idempotency-Key header (UUID v4). See Idempotency. That is separate from correlationId. Reusing a key with a different body (for example a new fingerprint payload) can return 409. Successful replays may include Idempotency-Replayed: true. Idempotency is best-effort if the backing store is unavailable.
Success response (HTTP 200)
| Field | Description |
|---|---|
id | Stitch event resource id. Use for support and GET by event. Opaque; persist as returned. |
correlationId | Your business reference if you sent one, otherwise one Stitch assigned. Not used for idempotency. |
userId | Echo of the optional userId you sent, if any. |
deviceId | Opaque device identifier scoped to your client. Persist as returned; do not parse structure from examples. Primary output for risk decisions. Presence means resolution succeeded. May change after prolonged inactivity; treat stored ids as soft references and handle 404 on device reads. |
metadata | Your metadata, echoed as submitted. |
signals | Curated device intelligence. See Signals. |
createdAt | When Stitch received the event. |
Success means HTTP 200 with a deviceId.
{
"id": "fpe_1Q0PsIJvEtkwdCNYMSaVuR",
"correlationId": "login-123",
"userId": "user-abc",
"deviceId": "fpd_2eyfk4nQpHs7iWQ4QwjJK5",
"metadata": {
"action": "login",
"channel": "my-app-web"
},
"signals": {
"platform": "web",
"ip": {
"address": "102.x.x.x",
"country": "ZA"
},
"device": {
"browser": { "name": "Chrome", "version": "126.0" },
"os": { "name": "macOS", "version": "15.1" },
"userAgent": "Mozilla/5.0 ...",
"screenResolution": { "width": 1440, "height": 900 },
"timezone": "Africa/Johannesburg",
"languages": ["en-ZA", "en"]
}
},
"createdAt": "2026-05-28T17:00:00Z"
}
Errors
Validation, auth, and processing failures use the standard Stitch API Gateway error response:
{
"code": "BAD_USER_INPUT",
"title": "Invalid Input",
"detail": "fingerprint is required"
}
| HTTP | Typical code | Meaning |
|---|---|---|
| 400 | BAD_USER_INPUT / INVALID_IDEMPOTENCY_KEY | Invalid request (for example missing fingerprint, oversized metadata, or a bad idempotency key). |
| 401 | UNAUTHORIZED | Missing or invalid access token. |
| 403 | FORBIDDEN | Token lacks the client_fingerprinting scope, or the client is not enabled. |
| 409 | IDEMPOTENCY_KEY_ALREADY_USED / IDEMPOTENCY_REQUEST_IN_PROGRESS | Idempotency conflict (same key, different body, or request still in flight). |
| 500 | INTERNAL_SERVER_ERROR | Unexpected failure. Retry the request if appropriate. |
Do not invent a deviceId when the request fails.
Read APIs
All reads require a client token with the client_fingerprinting scope and are scoped to your client. Path parameters are resource primary keys only (id). Alternate lookups use query parameters.
Persist anything you need long-term from the POST response on your side. Do not rely on event GETs as an archive. GET /devices/{id} may return 200 with id / lastSeenAt and omit signals when a snapshot is unavailable.
Get event by id
GET https://api.stitch.money/v2/fingerprint/events/{id}
Authorization: Bearer {access_token}
Re-fetch a completed event (for example if a POST response was lost, or for support). This is not waiting on pending resolution. POST is synchronous and already terminal.
Use the id from the submit response. Do not put a correlationId or deviceId in this path; those are different lookups below. A wrong key returns 404.
On success, the response body matches the POST 200 success shape (id, deviceId, metadata, signals, etc.).
| HTTP | Typical code | Meaning |
|---|---|---|
| 200 | - | Event found; same success body as submit. |
| 404 | NOT_FOUND | Unknown event id. |
| 401 | UNAUTHORIZED | Missing or invalid access token. |
| 403 | FORBIDDEN | Token lacks the client_fingerprinting scope, or the client is not enabled. |
List events (paginated)
Event collections use the same pagination style as other Stitch REST list APIs: limit, offset, and hasNext. Results are newest first.
Pass one filter per request:
| Query | Purpose |
|---|---|
correlationId | Find events by the business reference you sent (for example a lost event id) |
deviceId | Event history for that device (newest first). Latest event only: add &limit=1. |
GET https://api.stitch.money/v2/fingerprint/events?correlationId={correlationId}&limit=50&offset=0
GET https://api.stitch.money/v2/fingerprint/events?deviceId={deviceId}&limit=50&offset=0
Authorization: Bearer {access_token}
{
"data": [
{
"id": "fpe_1Q0PsIJvEtkwdCNYMSaVuR",
"correlationId": "login-123",
"deviceId": "fpd_2eyfk4nQpHs7iWQ4QwjJK5",
"signals": { }
}
],
"page": {
"limit": 50,
"offset": 0,
"hasNext": false
}
}
| HTTP | Typical code | Meaning |
|---|---|---|
| 200 | - | Page returned (data may be empty). |
| 400 | BAD_USER_INPUT | Missing filter, or invalid pagination params. |
| 401 | UNAUTHORIZED | Missing or invalid access token. |
| 403 | FORBIDDEN | Token lacks the client_fingerprinting scope, or the client is not enabled. |
Get device by id
GET https://api.stitch.money/v2/fingerprint/devices/{id}
Authorization: Bearer {access_token}
Returns the device resource for a public device id you previously received (as deviceId on events). This is not an event.
| Field | Description |
|---|---|
id | Public device id (same value as deviceId on events). |
lastSeenAt | Last successful resolve for this device+client. |
signals | Latest curated signals snapshot when available; omit if aged out / unknown. |
For the latest full event (including correlationId, metadata, etc.), use GET /v2/fingerprint/events?deviceId={id}&limit=1. For history, omit limit=1 and paginate. Submit a new POST when you need freshly collected intelligence.
{
"id": "fpd_2eyfk4nQpHs7iWQ4QwjJK5",
"lastSeenAt": "2026-07-29T12:00:00.000Z",
"signals": {
"platform": "web",
"ip": { "address": "102.x.x.x", "country": "ZA" },
"device": {
"browser": { "name": "Chrome", "version": "126.0" },
"os": { "name": "macOS", "version": "15.1" },
"screenResolution": { "width": 1440, "height": 900 }
}
}
}
| HTTP | Typical code | Meaning |
|---|---|---|
| 200 | - | Device known. May include signals, or only id / lastSeenAt if a signals snapshot is unavailable. |
| 404 | NOT_FOUND | Unknown device id, or not owned by your client (not merely "signals expired"). |
| 401 | UNAUTHORIZED | Missing or invalid access token. |
| 403 | FORBIDDEN | Token lacks the client_fingerprinting scope, or the client is not enabled. |
Listing events by userId is not available in this release.
Metadata
Metadata is optional freeform context stored with the event and returned as submitted. It does not participate in identity resolution. Use a flat object.
A typical submit is fingerprint payload + optional userId + optional metadata.action (and any other keys you care about, such as channel).
| Suggested key | Example | Notes |
|---|---|---|
action | "login" | What the user was doing. Omit if you prefer not to share. |
channel | "my-app-web" | Product surface. Example only. |
Limits: Metadata must be a flat JSON object (no nested objects or arrays); nesting returns HTTP 400. Total size at or below 4KB UTF-8 bytes of the serialized JSON. Stitch does not validate individual value formats (no phone or email checks). Oversized metadata returns HTTP 400.
userId is not metadata. Send it as an optional top-level request field when known.
Signals
signals is curated device intelligence for this event, not a copy of the SDK payload. Keys are only included when known for that platform and event. A missing key means unknown, not false.
| Key | Platforms | What it tells you |
|---|---|---|
platform | All | web, ios, or android |
ip.address | All | IP observed at SDK collection when signed fingerprintContext is present and valid. Absent if context is missing, expired, or not enabled for your client (missing = unknown, not an error). Signed context is short-lived (on the order of ~5 minutes). Collect close to submit; a queued or delayed POST can drop ip.* without failing the request. |
ip.country | All | ISO country code from that IP when attested context is available |
device.os | All | Object { "name", "version" } when available |
device.browser | Web | Object { "name", "version" } when available |
device.userAgent | When available | User agent string when available |
device.screenResolution | When available | Object { "width", "height" } (numbers when known) |
device.timezone | When available | Device timezone |
device.languages | When available | Language preferences |
"signals": {
"platform": "web",
"ip": {
"address": "102.x.x.x",
"country": "ZA"
},
"device": {
"browser": { "name": "Chrome", "version": "126.0" },
"os": { "name": "macOS", "version": "15.1" },
"userAgent": "Mozilla/5.0 ...",
"screenResolution": { "width": 1440, "height": 900 },
"timezone": "Africa/Johannesburg",
"languages": ["en-ZA", "en"]
}
}
Additional browser and mobile integrity signals (bot, VPN, emulator, jailbreak, and similar) are phased and appear later as detectors ship. Stitch will communicate new signal availability as they become available; missing keys do not break deviceId or event id.
Not returned in signals: raw SDK attribute bags, matcher internals, or an IP “collection vs API submit” proof (API submit is your server-side, so that comparison is not meaningful).
Operations
| Topic | Guidance |
|---|---|
| Network | Allow egress to *.stitch.money from the app (the SDK fetches config automatically). |
| Lost response / re-read | GET /v2/fingerprint/events/{id}, or GET /v2/fingerprint/events?correlationId= if you never received the event id. |
| Device resource / latest signals snapshot | GET /v2/fingerprint/devices/{id}; POST again for freshly collected signals. |
| Latest full event for a device | GET /v2/fingerprint/events?deviceId={id}&limit=1 (newest first). |
| Event history for a device | GET /v2/fingerprint/events?deviceId= with limit / offset pagination. |
deviceId longevity | Expect stability with regular use. After prolonged inactivity (months), a returning device may receive a new deviceId. |
| Local cache | The SDK may return a recently cached fingerprint payload from a prior getDeviceData() call. That is still a local SDK result — you must still submit it from your server-side via POST /v2/fingerprint/events. Prefer calling getDeviceData() close to the action you care about when you want a fresh payload. |
| Safe retries | Use the Gateway Idempotency-Key header for the same HTTP request. Do not reuse a key with a different body. Idempotency is best-effort if the backing store is unavailable. |