# Callback payload

> Exactly what ZeroCaptcha POSTs to your callback URL in each format, its headers, how its signature is computed, and how deliveries are retried.

Source: https://zerocaptcha.io/docs/reference/callbacks

When a task that named a callback URL ends, we POST its result to that URL, once it is final:
succeeded, failed or expired. This page is the reference for that request. For how to name a URL
and check a call, see [Polling and callbacks](https://zerocaptcha.io/docs/callbacks).

## The request

| Part | Value |
| --- | --- |
| Method | `POST` |
| URL | The task's `callbackUrl`, or `pingback` in the 2Captcha format |
| `Content-Type` | `application/json` for REST and createTask tasks; `application/x-www-form-urlencoded` for 2Captcha tasks |
| `User-Agent` | `ZeroCaptcha-Callbacks/1.0` |
| `ZeroCaptcha-Signature` | `t=<unix seconds>,v1=<64 hex digits>` |
| `ZeroCaptcha-Delivery` | The delivery's ID, a UUID, the same on every attempt |

The body is read from the task when each attempt is made, in the format the task was created in.

## The signature

`v1` is the HMAC-SHA256, in lowercase hex, of the timestamp `t`, a full stop, and the raw body, keyed
with your account's callback secret (`zcsig_…`):

```text
v1 = hex(HMAC-SHA256(key = callback secret, message = t + "." + body))
```

`t` is when the attempt was made, so it changes on each retry, and so does `v1`. Accept a call only
if `v1` matches in a constant-time comparison and `t` is within five minutes of your clock. Code in
four languages is in [Polling and callbacks](https://zerocaptcha.io/docs/callbacks#check-the-signature).

## REST tasks

The task as `GET /v1/tasks/{id}` shows it, with `solution` while its token is valid:

```json
{
  "id": "0192f3a4-7b1c-7d2e-9f10-3c4d5e6f7a8b",
  "type": "TurnstileTaskProxyless",
  "kind": "turnstile",
  "status": "succeeded",
  "websiteURL": "https://shop.example.com/login",
  "websiteKey": "0x4AAAAAAAB1cD2eF3gH4iJ5",
  "action": null,
  "cdata": null,
  "usesProxy": false,
  "price": "0.000800",
  "held": "0.000000",
  "cost": "0.000800",
  "attempts": 1,
  "maxAttempts": 3,
  "errorCode": null,
  "errorDescription": null,
  "solution": { "token": "0.Zm9vYmFy…", "userAgent": null, "cookie": null },
  "tokenState": "available",
  "tokenIssuedAt": "2026-09-30T14:02:14Z",
  "tokenExpiresAt": "2026-09-30T14:07:14Z",
  "idempotencyKey": null,
  "createdAt": "2026-09-30T14:02:05Z",
  "startedAt": "2026-09-30T14:02:06Z",
  "finishedAt": "2026-09-30T14:02:14Z",
  "deadline": "2026-09-30T14:04:35Z",
  "updatedAt": "2026-09-30T14:02:14Z",
  "version": 3
}
```

A failed or expired task has `status` `failed` or `expired`, its `errorCode` and `errorDescription`,
`cost` `0.000000` and no `solution`. A challenge page's solution has `userAgent` and `cookie` too;
see [Cloudflare WAF and 5-second challenges](https://zerocaptcha.io/docs/challenges#read-the-result). Every field is in the
[API reference](https://zerocaptcha.io/docs/reference/api/tasks).

## createTask tasks

The reply `getTaskResult` would give at that moment:

```json
{
  "errorId": 0,
  "taskId": "0192f3a4-7b1c-7d2e-9f10-3c4d5e6f7a8b",
  "status": "ready",
  "solution": { "token": "0.Zm9vYmFy…", "type": "turnstile" },
  "cost": "0.000800",
  "createTime": 1790776925,
  "endTime": 1790776934,
  "solveCount": 1,
  "expiresAt": "2026-09-30T14:07:14Z"
}
```

A task that did not succeed sends `errorId: 1` with `errorCode`, `errorDescription`, `taskId`,
`"status": "failed"` and `"cost": "0.000000"`. See [createTask format](https://zerocaptcha.io/docs/createtask).

## 2Captcha tasks

A form with the task's ID and its token:

```text
id=0192f3a4-7b1c-7d2e-9f10-3c4d5e6f7a8b&code=0.Zm9vYmFy...
```

or, when the task was not solved, the code in place of the token, such as
`code=ERROR_CAPTCHA_UNSOLVABLE`. See [2Captcha format](https://zerocaptcha.io/docs/2captcha#error-codes).

## Deliveries and retries

| | |
| --- | --- |
| Success | Any 2xx status. The body of your answer is ignored. |
| Time limit | 10 seconds per attempt |
| Retries | After 30 seconds, then doubling (1, 2, 4, 8, 16 and 32 minutes), each wait lengthened by up to half at random |
| Attempts | 8 in all, over roughly 65 to 95 minutes; then the delivery stops |
| Redirects | Not followed: a 3xx is a failed attempt |
| Addresses | The URL's name is resolved on every attempt; a private, loopback or link-local address is never called, and the delivery stops at once |

A delivery can arrive more than once: use `ZeroCaptcha-Delivery` or the task ID to handle each task
once. Because each attempt reads the task afresh, a retry made after a Turnstile token expired
carries the task without its token; read failed deliveries' tasks from the API instead.
