Status
After you submit a release, monitor its progress with two endpoints — one for store delivery and one for the background processing job. Both require authentication.
Delivery & aggregate status
curl -H "Authorization: Bearer $ONCE_ACCESS_TOKEN" \
https://once.app/v1/releases/<id>/statusGET /v1/releases/:id/status returns per-store delivery state plus an
aggregate status for the release. Poll this after submission to follow the
release out to the stores.
A release that has not reached the distributor yet returns a queued response
(aggregateStatus: "queued" with pending: true) instead of an empty result.
Errors are specific: a malformed release id, a release that does not exist,
and a release owned by another account each return a distinct error message,
so your integration can react correctly instead of treating every failure as
forbidden. When live store statuses cannot be fetched, the response falls back
to the last known statuses and includes fallback: true plus a
fallbackReason explaining why.
Processing job
curl -H "Authorization: Bearer $ONCE_ACCESS_TOKEN" \
https://once.app/v1/releases/<id>/jobGET /v1/releases/:id/job returns the background processing job’s state and
any errors. Use it to diagnose a release that is stuck or failed before it
reaches the delivery stage.
Release status webhooks
Instead of polling, register webhook endpoints and let ONCE push status changes to you. Webhooks are the recommended pattern; use polling as a fallback or for on-demand checks.
Register up to 5 https endpoints, either in Settings > Developer in
the ONCE app or over the REST API. Each
endpoint gets a signing secret that is shown once at creation, so copy it
immediately.
On every release status change, ONCE sends a POST to each active endpoint
with the event release.status_changed:
{
"event": "release.status_changed",
"releaseId": "uuid",
"title": "My Release",
"artist": "Artist Name",
"previousStatus": "delivered",
"status": "distributed",
"storeStatuses": [
{
"storeId": 9,
"storeName": "Spotify",
"statusText": "Live",
"urlInStore": "https://open.spotify.com/album/..."
}
],
"occurredAt": "2026-07-04T12:00:00.000Z"
}Every delivery carries two headers:
x-once-event: the event type (release.status_changed)x-once-signature:sha256=<hex>, the HMAC-SHA256 of the raw request body computed with your endpoint’s signing secret
Verify each delivery by computing the HMAC-SHA256 of the raw body with your
secret and comparing it to the hex digest in x-once-signature.
Delivery and retries: failed deliveries are retried twice. An endpoint
that fails 20 deliveries in a row is disabled automatically; you can re-enable
it from Settings > Developer, or with a PATCH request, once it is healthy
again.
Manage webhooks over the API
Headless integrations can manage webhook endpoints directly with a bearer token (PAT or OAuth), with the same validation and limits as the in-app UI.
List your endpoints (signing secrets are never returned):
curl -H "Authorization: Bearer $ONCE_ACCESS_TOKEN" \
https://once.app/v1/webhooksRegister an endpoint. The response includes the signing secret exactly once, so store it immediately:
curl -X POST https://once.app/v1/webhooks \
-H "Authorization: Bearer $ONCE_ACCESS_TOKEN" \
-H 'Content-Type: application/json' \
-d '{"url": "https://example.com/hooks/once", "description": "Production listener"}'Disable an endpoint, or re-enable one that was auto-disabled after repeated failures (re-enabling resets the failure count):
curl -X PATCH https://once.app/v1/webhooks/<id> \
-H "Authorization: Bearer $ONCE_ACCESS_TOKEN" \
-H 'Content-Type: application/json' \
-d '{"active": true}'Delete an endpoint:
curl -X DELETE https://once.app/v1/webhooks/<id> \
-H "Authorization: Bearer $ONCE_ACCESS_TOKEN"A POST beyond the 5-endpoint limit returns 409 conflict_error, a
non-https URL returns 400 invalid_request_error, and an endpoint id that
does not belong to your account returns 404 not_found_error.
Polling guidance
Status moves through asynchronous stages, so poll on an interval rather than
expecting an immediate final state. Respect the Retry-After header if you
receive a 429 rate_limit_error, and treat a 502 upstream_error as
transient — retry with backoff.
Tip: Prefer webhooks over polling when you need to react to status changes. Polling remains fully supported for on-demand checks.