> AffiliateOS Help Center. Index of every article for AI tools: https://www.affiliateos.ai/help/llms.txt

# How to answer a postback, timeouts and retries

Postbacks and conversions · For publishers, networks · Updated 30 October 2026
Web version: https://www.affiliateos.ai/help/postbacks/answer-postback-timeouts-retries

Store the postback, answer with any 2xx status, then credit the user. We judge your answer by its status code alone: a 2xx ends the delivery, a timeout, a 5xx or a 429 gets a quick retry on publisher postbacks, and any other answer is final.

## What we do with your answer

| Your server's answer | What we record | Do we try again? |
|---|---|---|
| Any 2xx, such as 200 or 204 | sent | No |
| A 5xx, such as 500 or 503 | failed, if every try fails | Yes, on publisher postbacks |
| 429 | failed, if every try fails | Yes, on publisher postbacks |
| Any other 4xx, such as 400, 401 or 404 | failed | No |
| No answer in time, or no connection at all | failed, with the error | Yes, on publisher postbacks |

We never judge the body of your answer. It may show under **Response** in **Flight Recorder**, but a 200 that says "error" in its body still counts as sent, and a sent delivery means your server answered, not that it credited anyone.

## Timeouts and retries

- **Publisher postbacks get up to three tries.** That covers the Offer API, the Top Offers Feed, Offer Links and the Hosted Offerwall. The second and third tries follow a fraction of a second after the one before.
- **All the tries share one time limit of 20 seconds at most.** A server that hangs on the first try uses it up, so a slow answer gets no second try. Answer within a few seconds.
- **Network postbacks are sent once.** There is no retry on a timeout, a 5xx or a 429.
- **Nothing is resent later on its own.** After the last try, the delivery stays failed.

> **A failed postback isn't resent automatically.** The conversion still counts in your reports and still pays you. To catch up after downtime, pull the missed conversions with the [Reporting API](https://www.affiliateos.ai/help/postbacks/pull-conversions-reporting-api). Publishers can also send us the `action_id` values: our team can send failed credits from the Offer API, the feed and links again, under the same `action_id`, to the URL you have saved now. Hosted Offerwall credits can't be resent.

## Answer first, then credit

Do the slow work after you answer. In order:

1. Check the `verifier`. If it doesn't match, answer 401 and credit nothing. A 401 is final, so we don't send it again.
2. Store the postback under its `action_id` and `status` together. If that pair is already stored, answer 200 and stop: it's a repeat.
3. If storing fails, answer 500. On publisher postbacks we try again.
4. Answer 200.
5. Credit the user on `completed`, deduct on `reversed`, from what you stored, in a queue or a background job.

Retries mean the same postback can reach you twice. If your server credits the user and then answers 500, we send it again. Storing first and checking the pair makes the second copy harmless. Dedupe on `action_id` alone and you drop the reversal of a conversion you already credited, because a [reversal](https://www.affiliateos.ai/glossary#reversal) repeats the original [action ID](https://www.affiliateos.ai/glossary#action-id).

```js
// Example receiver for Pebble Rewards (Express).
app.get("/afos", async (req, res) => {
  if (!verified(req)) return res.sendStatus(401);
  const { action_id, status } = req.query;
  let isNew;
  try {
    // Unique on (action_id, status): a repeat inserts nothing.
    isNew = await db.insertOnce(action_id, status, req.query);
  } catch {
    return res.sendStatus(500); // publisher postbacks are retried
  }
  res.sendStatus(200);
  if (isNew) queue.push({ action_id, status });
});
```

`verified()` is the signature check in [Verify a signed postback with HMAC-SHA256](https://www.affiliateos.ai/help/postbacks/verify-postback-secret). Point your postback URL at the endpoint that answers, not at a redirect.

## Find a failed delivery

Publishers: open **Reporting**, then **Flight Recorder**, and set **Delivery** to failed. Open a conversion: **Your postback delivery** shows when we tried (**Attempted**), the **HTTP status** your server returned, the **Host**, **Path** and **Masked URL** we called, and the **Error** when there was no answer. **Copy request** copies the URL and its parameters, with secrets masked.

Networks: **Reporting**, then **Flight Recorder** has the same **Delivery** filter. Open a postback and read **Network Delivery Response**: **Attempted**, **Status**, **HTTP**, **Host**, **Path**, **Masked URL**, **Error** and **Response**.

> Figure: Example trace for click clk_7Q2f81 from user u_48213: click recorded at 2:06 PM, install received, payout of $4.20 recorded, and the postback delivered to api.pebble.example, which answered 200.

Two delivery results aren't about your answer at all:

- **failed** with an error about a postback secret means nothing was sent: publisher postbacks are never sent unsigned. Create the secret under **Postbacks**.
- **skipped_no_url** means no postback URL matched the conversion. [How to set up a postback URL](https://www.affiliateos.ai/help/postbacks/set-up-postback-url) covers which row takes which step.
