Key takeaways

  • A postback is a webhook used in affiliate and offerwall tracking: usually a GET request with values filled into the query string, while general webhooks are usually POST requests with a JSON body.
  • Both are plain HTTP calls anyone could fake, so verify an HMAC signature with constant-time comparison, and treat an IP allow-list as an extra layer only.
  • Both can arrive more than once, so key every change on a stable ID and handle each status, including reversals, exactly once.

Webhook vs postback: what is the difference?

A webhook is an HTTP request one system sends to a URL you choose when something happens, so you do not have to keep asking. A postback is a webhook used in affiliate marketing, offerwalls, and ad tracking to report a conversion: who converted, for what amount, and with which status. In practice the difference is mostly shape. Postbacks are usually GET requests with the values filled into the query string through macros, while modern webhooks are usually POST requests with a JSON body and a signature in a header.

So every postback is a webhook, but not every webhook is a postback. If you already know how to build a safe webhook endpoint, you know most of what a postback handler needs. This page compares the two side by side and points out where the habits differ.

What is a webhook, and what is a postback?

What is a webhook?

A webhook is a push notification between servers. You register an endpoint URL with a service, and the service calls it when an event happens: a payment succeeds, a repository gets a push, a subscription renews. Stripe’s webhook documentation describes sending events to your endpoint as a JSON payload over HTTPS, and GitHub’s guide to creating webhooks says the JSON payload is delivered as the body of a POST request (or as a form field called payload if you pick the form-encoded content type). One webhook endpoint usually receives many event types, and the body tells you which one it is.

What is a postback?

A postback is the conversion report in server-to-server (S2S) tracking. You enter a postback URL with placeholders called macros, such as {user_id} or {status}, and the platform replaces each macro with a real value and calls the URL when a conversion is recorded or changes. The receiver decides the parameter names; the sender decides the macro names. A postback carries one kind of event, a conversion, and its status says what happened to it.

You will also hear “S2S postback”, “server postback”, and “callback URL”. They all mean the same thing: a server call, not a browser pixel. If you are choosing between a server call and a browser tag, postback vs pixel tracking covers that decision.

Who sends to whom

Webhooks usually flow from a platform to its customer: Stripe tells your shop that a charge succeeded, GitHub tells your CI server that code was pushed. The sender owns the event and you subscribe to it.

Postbacks often come in a chain, because a conversion passes through several parties:

  1. Advertiser to network. The user completes an offer, and the advertiser’s server (or its tracker) calls the network’s postback URL with the click ID it received in the tracking link.
  2. Network to publisher. The network matches the click, records the conversion, and calls the publisher’s postback URL with the user ID, the reward, and a status.
  3. Publisher to its own systems. The publisher credits the user, and may fire internal webhooks to a chat bot, an analytics tool, or a fraud queue.

Each hop only needs the ID it was given (see how click IDs and sub IDs work).

Example requests side by side

A typical postback

The publisher saves a URL with macros in the platform’s dashboard:

https://yoursite.com/postback?user={user_id}&tx={transaction_id}&event={event_id}&status={status}&reward={reward}&payout={payout}&hash={hash}

When a conversion is credited, the request that arrives looks like this (illustrative values only):

GET /postback?user=4821&tx=8c41f0a2&event=e5b7d913&status=1&reward=150&payout=0.42&hash=3f9c0d...b71a HTTP/1.1
Host: yoursite.com
User-Agent: Sharklio-Postback/1.0

Everything is in the URL. There is no body to parse, and the signature is just another parameter.

A typical webhook

A webhook in the style of the Standard Webhooks specification puts the event in a JSON body and the metadata in headers (illustrative values only):

POST /webhooks HTTP/1.1
Host: yoursite.com
Content-Type: application/json
webhook-id: msg_2KWPBgLlAfxdpx2AI54pPJ85f4W
webhook-timestamp: 1792656000
webhook-signature: v1,K5oZfzN95Z9UVu1EsfQmfVNQhnkZ2pj9o9NDN/H/pI4=

{"type": "conversion.credited", "data": {"user_id": "4821", "transaction_id": "8c41f0a2", "reward": 150}}

Here you read the raw body, check the signature over it, and only then parse the JSON to find the event type.

Webhook vs postback comparison table

AspectTypical postbackTypical webhook
Main useReporting conversions in affiliate, offerwall, and ad trackingAny event: payments, code pushes, messages, account changes
HTTP methodGETPOST
Where the data livesQuery string, filled in through macrosRequest body, usually JSON
Who designs the formatYou pick parameter names; the sender fixes the macro listThe sender fixes the whole payload
SignatureOften a hash parameter over selected values, or a shared key in the URLUsually an HMAC header over the raw body, often with a timestamp
Event typesOne kind (a conversion) with a status, such as credited or reversedMany types, named in the payload
Stable ID for deduplicationTransaction ID, plus an event ID per call on some platformsEvent or message ID
RetriesVaries by platform; many retry on errors and timeoutsVaries: Stripe retries for up to three days in live mode, GitHub does not retry automatically
OrderingVaries; some platforms guarantee order per transactionOften not guaranteed

The table describes common practice, not a law. Some platforms send JSON postbacks, so read the sender’s docs before you write a handler.

Security: signatures, HMAC, and IP allow-lists

A webhook or postback endpoint is a public URL. Anyone who learns it can call it with whatever values they like, so a request arriving is never proof that the event happened. The fix is the same for both: a signature that only the sender can produce.

HMAC signatures

Most senders sign with HMAC, the keyed-hash construction defined in RFC 2104 as “a mechanism for message authentication using cryptographic hash functions”. The sender and receiver share a secret key; the sender computes HMAC-SHA256 over the message, and the receiver computes it again and compares. A full walkthrough is in what is HMAC. What differs between webhooks and postbacks is what gets signed:

  • Webhooks usually sign the raw body. GitHub’s guide to validating webhook deliveries says the signature arrives in the X-Hub-Signature-256 header as an HMAC hex digest that starts with sha256=. Stripe signs the timestamp, a dot, and the raw JSON body, and sends the result in the Stripe-Signature header. If your framework parses and re-serializes the JSON before you check it, the bytes change and the check fails.
  • Postbacks usually sign selected values. A query string has no single raw body to sign, so senders hash the values that matter. On Sharklio, {hash} is the HMAC-SHA256, as lowercase hex, of the transaction ID, user ID, reward, and status joined with colons, signed with your postback secret key. Hash the values exactly as they arrive: do not cast the reward to a number and back first.

Both GitHub and Stripe tell you to compare signatures in constant time, so the comparison does not leak how many characters matched. Use hash_equals in PHP, crypto.timingSafeEqual in Node.js, or hmac.compare_digest in Python. Postback security has the full threat model and a complete handler.

Timestamps and replays

A valid request can be captured and sent again. Webhook senders often put a timestamp inside the signed content so you can reject old copies: Stripe says its libraries use a default tolerance of five minutes between the timestamp and the current time (checked on October 22, 2026). Postbacks rarely carry a signed timestamp, so the defense there is idempotency: a replayed transaction ID changes nothing because you already processed it.

IP allow-lists

Many senders publish the addresses they call from. Stripe lists its webhook IP addresses, and GitHub’s webhook best practices point to its GET /meta endpoint and warn that the addresses change from time to time. Sharklio postbacks come from 187.124.177.111 (IPv4) and 2a02:4780:79:782c::1 (IPv6). An allow-list blocks noise, but it is an extra layer: always verify the signature as well.

Mistakes that break signature checks

Checking a webhook signature against parsed and re-encoded JSON instead of the raw body. Reformatting a postback value (trimming, rounding, casting) before hashing it. Mixing up keys, for example using a link signing salt where the postback key belongs. Leaving CSRF protection on a webhook route, which Stripe notes can block legitimate events in frameworks like Rails and Django. Answering a failed check with 200, which hides attacks in your logs.

Retries, ordering, and idempotency

Networks fail, servers restart, and deploys take a few seconds. Senders deal with that by retrying, which means your endpoint must expect the same event more than once.

How senders retry

Retry rules differ a lot, so check them for every integration (all checked on October 22, 2026):

  • Stripe says it retries with exponential backoff for up to three days in live mode, and three times over a few hours in a sandbox. It does not guarantee that events arrive in the order they were created.
  • GitHub says it does not automatically redeliver failed deliveries (you redeliver them yourself), and its best practices ask you to answer with a 2XX within 10 seconds.
  • Sharklio postbacks expect any 2xx status within 6 seconds. The response body is not read and redirects are not followed. If your server fails or times out, the postback is tried again up to 5 times, after about 1 minute, 5 minutes, 30 minutes, 2 hours, and 6 hours. Events of one transaction arrive in order, and a reversal is never sent before the credit it cancels.

The common advice holds for both: answer fast, then do slow work afterwards. If a handler calls other services or sends email before it answers, move that work to a queue.

Idempotency: which ID to key on

An idempotent handler gives the same result no matter how many times the same event arrives. The question is which ID you store:

  • Webhooks give each event an ID. Stripe tells you to log processed event IDs and skip ones you have seen, and the Standard Webhooks spec says to use the webhook-id header as an idempotency key.
  • Postbacks usually give you a transaction ID that stays the same across status changes. On Sharklio, {transaction_id} is identical on the credit and on a later reversal, while {event_id} is unique for every event and is meant for logging. Store the transaction ID with the last status you processed, and skip repeats.

Storing the ID and changing the balance must happen together, inside one database transaction with a unique key, or two copies arriving at the same moment can both get through. How to prevent duplicate reward credits shows the table design and the locking pattern.

Statuses: credit vs reversal

A webhook stream has many event types, and you subscribe to the ones you need. A postback has one subject, a conversion, and the status field tells you what happened to it. Common statuses across platforms include pending, approved or credited, rejected, and reversed. A reversal (also called a chargeback) means a conversion that was credited earlier was taken back, for example because the advertiser found fraud or the user canceled.

Sharklio uses two statuses:

{status}{state}What your server does
1creditedAdd {reward} to the user, once per transaction ID
2reversedTake {reward} back, only if you credited that transaction before; otherwise ignore it

Amounts are never negative: the status, not the sign, tells you whether to add or subtract. This is a key difference from many webhooks, where a refund arrives as a separate event type with its own object. If you treat a postback like a “payment succeeded” webhook and ignore every other status, users keep rewards for conversions you were never paid for. Publisher earnings and reversals explains why reversals happen.

One handler pattern for both

Verify the signature first and answer 403 if it fails. Then look up the stable ID. If the change is new, apply it and store the ID in the same database transaction. If it is a repeat, change nothing. Answer 2xx quickly in both cases, and log the event ID and the result.

Testing a webhook or postback handler

Test the unhappy paths, not only the first credit:

  • Bad signature: change one character of the hash or the body and confirm you get a 403 and no balance change.
  • Duplicate: send the same event twice and confirm the user is credited once.
  • Reversal: send a credit, then a reversal with the same transaction ID, and confirm the balance returns to where it started. Then send a reversal for an ID you never credited and confirm nothing happens.
  • Slow downstream: make a dependency hang and confirm you still answer inside the sender’s time limit.

Most senders have a test tool. Stripe has its CLI, GitHub lets you redeliver past deliveries, and on Sharklio, Send a test postback on the Integration tab sends a credit or a reversal for a user ID and payout you choose. A test always has a transaction_id that starts with test_ and offer_id 0, so your handler can tell tests from real results. Ready-made handlers are in the Node.js postback guide and the Python postback guide, and how to resend a failed postback covers recovery after an outage.

How Sharklio postbacks work

Sharklio is a pay-per-result affiliate network for websites, apps, games, and communities. When your users complete offers on your offerwall, rewards reach your server through a signed server-to-server postback for every credit and every reversal: a GET request with the macros you choose, an HMAC-SHA256 {hash}, retries, and every attempt listed in your dashboard. The full reference is in the Sharklio postback docs. See how the wall works.

Frequently asked questions

Is a postback the same as a webhook?

A postback is a kind of webhook. The term is used in affiliate marketing and ad tracking for webhooks that report conversions, usually as a GET request with values in the query string.

What is an S2S postback?

An S2S (server-to-server) postback is a conversion report sent directly from one server to another, without the user’s browser. It is the standard way affiliate networks and offerwalls confirm conversions, because ad blockers and cookie limits cannot stop it.

Why do postbacks use GET instead of POST?

Postback URLs are built from macros that the sender fills in, and a query string is the simplest place to put them: you can paste one URL into a dashboard with no body format to agree on. It is a convention, not a rule, so some platforms send POST postbacks.

Should a webhook endpoint check the IP address?

It can, as an extra layer, if the sender publishes its addresses. It should never replace the signature check: addresses change, and only the signature proves the values were not altered.

What should my endpoint return?

A 2xx status code, quickly. Most senders treat anything else, or a timeout, as a failure and retry. Return 403 for a failed signature check so attacks show up in your logs.

How do I stop the same postback crediting a user twice?

Store the transaction ID with the status you processed, under a unique key, in the same database transaction as the balance change. A repeat then finds the stored row and changes nothing.

Can a postback take money back?

Yes. A reversal postback carries the same transaction ID as the credit it cancels, and your server subtracts the reward if it credited that transaction before.