POST /v1/projects/:projectId/sdk-apps/:appId/verify-tracking
Active probe — writes a synthetic event into Layers and returns whether the SDK install is healthy, plus whether each CAPI destination is configured.
/v1/projects/:projectId/sdk-apps/:appId/verify-tracking- Auth
- Bearer
- Scope
- sdk:write
Active probe of an SDK install. Writes a synthetic event into Layers and reads it back; returns a structured health report including whether each CAPI destination is configured.
This is the "is tracking healthy right now?" check. The probe writes a synthetic event into Layers, polls until it is readable, and returns a structured report.
The synthetic event stays inside Layers. It is written straight to your event store and is never forwarded to Meta, TikTok, or any other ad platform, so it cannot appear in a debugger UI and cannot touch an advertiser's reporting. That is deliberate: Meta's Conversions API documents that a test-coded event is not isolated — "Events sent with test_event_code are not dropped. They flow into Events Manager and are used for targeting and ads measurement purposes." (Meta docs) A probe that reached Meta would therefore skew the very numbers you are trying to verify.
What the probe proves is the Layers half of the pipeline — the app is recognized, events round-trip, real traffic exists — plus, statically, whether your Meta and TikTok CAPI credentials are complete. To confirm a platform is actually receiving forwarded conversions, use GET /v1/projects/:id/ads/capi-status, which reports each platform's own received count.
Use this for:
- A "Test connection" button in your partner dashboard.
- Post-deploy verification — fire after rolling a new SDK install.
- Customer support — a structured diagnostic better than "send me a screenshot of Meta's debugger."
For passive signals (no synthetic event), see SDK health — lastEventAt, the events list, and capi-status are the right tools when you don't want to inject a test event.
Path parameters
projectIdstring (uuid)requiredProject the SDK app belongs to.appIdstringrequiredSDK app id.
Body
expectedEventsstring[]optionalEvent names you expect the SDK to be firing. Default: `["app_open"]`. The probe checks the timeline for each in the recent past and reports any missing in `missing[]`.eventNamestringoptionaldefault: "layers_verify_tracking"Event name for the synthetic test. Must start with `layers_`, so your own analytics can filter synthetic events out without matching a real event name.
Request
curl -X POST https://api.layers.com/v1/projects/$PROJECT_ID/sdk-apps/$APP_ID/verify-tracking \
-H "Authorization: Bearer $LAYERS_API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: $(uuidgen)" \
-d '{
"expectedEvents": ["app_open", "Purchase"]
}'Response
{
"status": "healthy",
"lastEventAt": "2026-05-08T17:02:11.123Z",
"expectedEvents": ["app_open", "Purchase"],
"observedEvents": ["app_open", "Purchase", "session_start"],
"missing": [],
"samples": [
{
"event": "Purchase",
"receivedAt": "2026-05-08T17:01:55.000Z",
"payload": { "value": 9.99, "currency": "USD" }
}
],
"checks": [
{ "name": "ingest_endpoint_reachable", "passed": true },
{ "name": "app_id_recognized", "passed": true },
{ "name": "capi_meta_configured", "passed": true },
{ "name": "capi_tiktok_configured", "passed": true },
{ "name": "test_event_round_trip", "passed": true }
],
"testEventCode": "TEST123",
"syntheticEventId": "0f7c1d5e-2b64-4a19-9d2f-7c3a55e18b40"
}{
"status": "no_install",
"lastEventAt": null,
"expectedEvents": ["app_open"],
"observedEvents": [],
"missing": ["app_open"],
"checks": [
{ "name": "ingest_endpoint_reachable", "passed": true },
{ "name": "app_id_recognized", "passed": true },
{ "name": "capi_meta_configured", "passed": false, "remedy": "Patch the SDK app's CAPI config: PATCH /v1/projects/:id/sdk-apps/:appId with capi.meta.{enabled, pixel_id, access_token_vault_id}." },
{ "name": "test_event_round_trip", "passed": false }
]
}status values
| Status | Meaning | Typical remedy |
|---|---|---|
healthy | Synthetic event arrived, expected events recently observed, all relays configured and dispatching. | None — you're done. |
missing_events | Test event arrived but one or more expectedEvents is absent from the recent timeline. | The SDK is installed but is not firing the events you expect. Check the SDK init code. |
schema_drift | Events arrive but with property shapes Meta / TikTok will reject (missing revenue on Purchase, missing currency, etc.). | Update the SDK call sites to include canonical properties. |
no_install | No events ever, or test event never arrived. | The SDK is not installed or not initialized. Walk through the install guide. |
Per-check breakdown
Every response carries checks[] regardless of status. Each check has name, passed, and (when failed) remedy — a one-line fix description.
| Check | What it confirms |
|---|---|
ingest_endpoint_reachable | Layers' ingest URL responds with 2xx. (Almost always passes — fails only during a Layers outage.) |
app_id_recognized | The appId exists in sdk_apps and is not soft-deleted. |
capi_meta_configured | If the project has a Meta CAPI config, it's complete (pixel id, vault token resolvable, enabled). Skipped if no Meta layer. |
capi_tiktok_configured | Same for TikTok. |
test_event_round_trip | The synthetic event arrived and was visible in sdk_events within the timeout. |
Probe semantics
- The probe writes the synthetic event straight into your Layers event store and never forwards it to Meta, TikTok, or any other ad platform. Nothing it does can reach a platform debugger or a platform's reporting.
testEventCode, when you supply one, is recorded on the synthetic event's properties and echoed back in the response so you can correlate it in your own downstream analytics. It stays inside Layers. It is a Layers-local correlation tag; Meta's owntest_event_codeis a different field with different semantics, and Meta states that events carrying it "are not dropped. They flow into Events Manager and are used for targeting and ads measurement purposes."- The probe does NOT count against the project's daily event quota.
- Multiple concurrent probes are fine — each uses a unique
testEventCode.
Errors
| Code | When |
|---|---|
NOT_FOUND | :projectId or :appId doesn't exist in this org. |
FORBIDDEN_SCOPE | Key lacks sdk:write (or legacy projects:write). |
RATE_LIMITED | More than 10 probes per minute per app. |
See also
- SDK health concept
GET /v1/projects/:id/ads/capi-status— passive CAPI healthPATCH /v1/projects/:id/sdk-apps/:appId— wire up CAPI configPOST /v1/events— server-side event forwarding