Skip to main content
View as Markdown

Webhook event types

Each webhook request body is one event. The envelope is the same for every type; data depends on type. Ids are UUIDs. Times are ISO 8601 in UTC. New fields can be added to data at any time, and the enums below can gain values, so ignore what you don't know.

TypeSent when
allocation.createdAn allocate request was accepted; the device may still be provisioning
allocation.readyA device is allocated and usable
allocation.failedNo device was allocated
allocation.releasedA device left the session
usage.threshold_reachedUsage crossed 80% or 100% of the period's included seconds
usage.quota_exceededAn allocate was refused because the usage quota is exhausted
app.uploadedAn uploaded app was verified and can be installed
app.upload_failedAn uploaded app failed verification
app.deletedAn uploaded app was deleted
test_run.startedA test result was created
test_run.finishedA test run reached passed, failed, flaky or errored
webhook.testYou sent a test event

When you add a webhook, pick exact types, a group such as allocation.* (which also covers types added to the group later), or all events (*). GET /api/v1/webhooks/event-types lists the types you can pick. webhook.test is not in it: it goes only to the webhook you test.

Allocation events​

An allocation is one device in one session, from the request until it is released. Every allocation.* event carries these fields in data:

Field
allocationThe allocation id; the same as device.id. Join the events of one allocation on it.
allocationStatusprovisioning, ready, released or failed. See Lifecycle rank.
sessionThe session the allocation belongs to. Absent when the request named none.
userThe user who requested the device.
deviceThe device, as GET /api/v1/sessions/{sessionId}/devices returns it. Absent until a device is assigned.

Lifecycle rank​

Events are delivered in no particular order, so a late allocation.ready can arrive after the allocation.released of the same allocation. Rank each event by its allocationStatus:

allocationStatusRank
provisioning1
ready2
released, failed3 (final)

To track an allocation's current state, keep the highest rank you have seen per allocation: an event with a lower rank never moves it back. Still process that event for anything else it tells you, such as when the device became ready. A final state never changes. When in doubt, read the session's devices from the API.

allocation.created​

An allocate request was accepted. data.allocationStatus is provisioning while a device is being prepared, or ready when one was available at once; in that case allocation.ready is sent too. data.status repeats the same value on this event only. data.requestedAt is when the request arrived.

{
"id": "dfea3d5e-5a36-492b-885e-aab6cffdec2c",
"object": "event",
"type": "allocation.created",
"apiVersion": "2026-09-22",
"createdAt": "2026-09-22T10:05:00.117Z",
"data": {
"allocation": "7f0c9a1e-2b3d-4c5e-8f90-a1b2c3d4e5f6",
"session": "b2c3d4e5-f6a7-4b8c-9d0e-1f2a3b4c5d6e",
"user": "9c84e1f0-1a2b-4c3d-8e4f-5a6b7c8d9e0f",
"allocationStatus": "provisioning",
"status": "provisioning",
"requestedAt": "2026-09-22T09:59:31.000Z"
}
}

allocation.ready​

The device is usable. data.device is present, and data.waitSeconds is the time from the request to ready.

{
"id": "7d368364-6535-417d-bade-8a75ecf93cac",
"object": "event",
"type": "allocation.ready",
"apiVersion": "2026-09-22",
"createdAt": "2026-09-22T10:05:00.117Z",
"data": {
"allocation": "7f0c9a1e-2b3d-4c5e-8f90-a1b2c3d4e5f6",
"session": "b2c3d4e5-f6a7-4b8c-9d0e-1f2a3b4c5d6e",
"user": "9c84e1f0-1a2b-4c3d-8e4f-5a6b7c8d9e0f",
"allocationStatus": "ready",
"device": {
"id": "7f0c9a1e-2b3d-4c5e-8f90-a1b2c3d4e5f6",
"status": "in_use",
"info": {
"platform": "ios",
"type": "real",
"name": "Apple iPhone 16 Pro",
"osVersion": "18.4",
"serial": "00008150-001C30842678401C"
},
"createdAt": "2026-09-22T10:00:04.000Z"
},
"waitSeconds": 33
}
}

allocation.failed​

No device was allocated. data.reason is one of no_device_available, quota_exceeded, concurrency_limit, provision_failed, provision_timeout or internal. When the allocate request itself was refused, data.filters holds its filters, and allocation is a fresh id made for the event because no allocation exists.

{
"id": "ef7238ef-580a-4457-97c0-70e33a465574",
"object": "event",
"type": "allocation.failed",
"apiVersion": "2026-09-22",
"createdAt": "2026-09-22T10:05:00.117Z",
"data": {
"allocation": "7f0c9a1e-2b3d-4c5e-8f90-a1b2c3d4e5f6",
"session": "b2c3d4e5-f6a7-4b8c-9d0e-1f2a3b4c5d6e",
"user": "9c84e1f0-1a2b-4c3d-8e4f-5a6b7c8d9e0f",
"allocationStatus": "failed",
"reason": "no_device_available",
"filters": [
{
"attribute": "platform",
"operator": "EQUALS",
"value": "ios"
}
]
}
}

allocation.released​

The device left the session.

Field
reasonuser (released by you or your code), idle, host_disconnected, or stale_record.
durationSecondsSeconds from ready to release.
billedSecondsSeconds billed for this allocation.
releasedAtWhen the release happened.

stale_record means Mobile Next repaired a record of a device it had lost track of. The device's real end time is unknown, billedSeconds is 0, and releasedAt is when the record was repaired.

{
"id": "50af8bdc-39bb-4a39-a56b-e90434bcfa05",
"object": "event",
"type": "allocation.released",
"apiVersion": "2026-09-22",
"createdAt": "2026-09-22T10:05:00.117Z",
"data": {
"allocation": "7f0c9a1e-2b3d-4c5e-8f90-a1b2c3d4e5f6",
"session": "b2c3d4e5-f6a7-4b8c-9d0e-1f2a3b4c5d6e",
"user": "9c84e1f0-1a2b-4c3d-8e4f-5a6b7c8d9e0f",
"allocationStatus": "released",
"device": {
"id": "7f0c9a1e-2b3d-4c5e-8f90-a1b2c3d4e5f6",
"status": "released",
"info": {
"platform": "ios",
"type": "real",
"name": "Apple iPhone 16 Pro",
"osVersion": "18.4",
"serial": "00008150-001C30842678401C"
},
"createdAt": "2026-09-22T10:00:04.000Z",
"releasedAt": "2026-09-22T10:05:00.117Z"
},
"reason": "user",
"durationSeconds": 296,
"billedSeconds": 300,
"releasedAt": "2026-09-22T10:05:00.117Z"
}
}

Usage events​

resource is the metered resource, today mobile. periodStart and periodEnd bound the current billing period.

usage.threshold_reached​

Usage crossed 80% or 100% of the seconds your plan includes for the period. Sent once per threshold, resource and period; one session can cross both thresholds at once and send two events. secondsIncluded is your plan's grant for the period, without coupons. secondsRemaining is the balance after the usage, coupons included. Organizations without an included amount get no threshold events.

{
"id": "a5082316-da84-4b69-b2cf-b83c9a53e24d",
"object": "event",
"type": "usage.threshold_reached",
"apiVersion": "2026-09-22",
"createdAt": "2026-09-22T10:05:00.117Z",
"data": {
"resource": "mobile",
"thresholdPercent": 80,
"secondsIncluded": 360000,
"secondsRemaining": 71940,
"periodStart": "2026-09-01T00:00:00Z",
"periodEnd": "2026-10-01T00:00:00Z"
}
}

usage.quota_exceeded​

An allocate request was refused because the organization is out of seconds. Sent once per resource and period.

{
"id": "38904bfa-d737-4f40-a9e4-070549362f06",
"object": "event",
"type": "usage.quota_exceeded",
"apiVersion": "2026-09-22",
"createdAt": "2026-09-22T10:05:00.117Z",
"data": {
"resource": "mobile",
"periodStart": "2026-09-01T00:00:00Z",
"periodEnd": "2026-10-01T00:00:00Z"
}
}

App events​

Sent for builds uploaded through Apps in the web app or the /files API. See Apps.

app.uploaded​

An uploaded build was verified and can be installed. file is its file id. When the build's bundle id was read, data also names the app: app (its id), appName, bundleId, platform (android or ios), version, buildNumber, and url, the app's page in the web app.

{
"id": "624460ae-210e-43ce-abfa-1ed812993a32",
"object": "event",
"type": "app.uploaded",
"apiVersion": "2026-09-22",
"createdAt": "2026-09-22T10:05:00.117Z",
"data": {
"file": "3e8a9b1c-4d5e-4f6a-8b7c-9d0e1f2a3b4c",
"filename": "shop.apk",
"filesize": 48213504,
"app": "6f186ef6-ff87-465b-924b-24796bb08a54",
"appName": "Acme Shop",
"bundleId": "com.acme.shop",
"platform": "android",
"version": "4.2.0",
"buildNumber": "4201",
"url": "https://app.mobilenext.ai/dashboard/apps/6f186ef6-ff87-465b-924b-24796bb08a54"
}
}

app.upload_failed​

An uploaded build failed verification. reason is the file's failureReason, such as checksum_mismatch, size_mismatch, unsupported_format or unsupported_architecture.

{
"id": "d88dfe9b-162f-4b8b-adad-bc93ad108615",
"object": "event",
"type": "app.upload_failed",
"apiVersion": "2026-09-22",
"createdAt": "2026-09-22T10:05:00.117Z",
"data": {
"file": "3e8a9b1c-4d5e-4f6a-8b7c-9d0e1f2a3b4c",
"filename": "shop.apk",
"reason": "checksum_mismatch"
}
}

app.deleted​

An uploaded build was deleted with Delete in the web app or DELETE /api/v1/files/{fileId}. A build that reaches its expiry date does not send it. data has the same fields as app.uploaded.

{
"id": "0b7c4e2a-9f1d-4c3b-8a6e-2d5f7a9c1e3b",
"object": "event",
"type": "app.deleted",
"apiVersion": "2026-09-22",
"createdAt": "2026-09-23T08:30:00.412Z",
"data": {
"file": "3e8a9b1c-4d5e-4f6a-8b7c-9d0e1f2a3b4c",
"filename": "shop.apk",
"filesize": 48213504,
"app": "6f186ef6-ff87-465b-924b-24796bb08a54",
"appName": "Acme Shop",
"bundleId": "com.acme.shop",
"platform": "android",
"version": "4.2.0",
"buildNumber": "4201",
"url": "https://app.mobilenext.ai/dashboard/apps/6f186ef6-ff87-465b-924b-24796bb08a54"
}
}

Test run events​

Sent for test results reported by Mobilewright or the /test-results API. data is the test result as GET /api/v1/test-results/{testResultId} returns it.

test_run.started​

A test result was created. A reporter that posts when the run begins sends status: "running"; one that posts after the run sends its final status, and test_run.finished follows at once.

{
"id": "c4b1f0d2-6e3a-4f57-9a0b-2d8e1c7f3a64",
"object": "event",
"type": "test_run.started",
"apiVersion": "2026-09-22",
"createdAt": "2026-11-03T10:00:00.084Z",
"data": {
"id": "7c1e2d3f-4a5b-4c6d-8e9f-0a1b2c3d4e5f",
"name": "Checkout flow",
"suite": "shop-app",
"userAgent": "mobilewright/0.0.39",
"git": { "repoUrl": "https://github.com/acme/shop-app", "branch": "main", "commitSha": "9f2c1e7" },
"status": "running",
"createdAt": "2026-11-03T10:00:00Z"
}
}

test_run.finished​

A run reached a final status: passed, failed, flaky or errored. Sent once per run, with the first final status; a later change to the run's status sends nothing.

isRecovered is true when this run did not fail and the previous finished run of the same suite on the same git.branch failed or errored: use it for "fixed" notifications. Runs reported in the same second, such as parallel shards, are not each other's previous run. It is false when the run has no suite or no branch.

{
"id": "e9a7d3b5-1f2c-4e8a-b6d0-3c5f7a9e1b42",
"object": "event",
"type": "test_run.finished",
"apiVersion": "2026-09-22",
"createdAt": "2026-11-03T10:03:04.512Z",
"data": {
"id": "7c1e2d3f-4a5b-4c6d-8e9f-0a1b2c3d4e5f",
"name": "Checkout flow",
"suite": "shop-app",
"userAgent": "mobilewright/0.0.39",
"git": { "repoUrl": "https://github.com/acme/shop-app", "branch": "main", "commitSha": "9f2c1e7" },
"stats": { "startTime": "2026-11-03T10:00:00Z", "duration": 184213, "expected": 42, "unexpected": 0, "skipped": 1, "flaky": 0 },
"status": "passed",
"createdAt": "2026-11-03T10:00:00Z",
"isRecovered": true
}
}

webhook.test​

Sent when you choose Send test event or call POST /api/v1/webhooks/{webhookId}/test, to that webhook only. data.webhook is the webhook's id.

{
"id": "4f0a2f60-8b23-4b15-a05d-ba45ad285bf6",
"object": "event",
"type": "webhook.test",
"apiVersion": "2026-09-22",
"createdAt": "2026-09-22T10:05:00.117Z",
"data": {
"webhook": "1d2e3f4a-5b6c-4d7e-8f9a-0b1c2d3e4f5a"
}
}