Skip to content

Postbacks

A postback is a URL on your own tracker that STX calls when a player you referred reaches a step. Your tracker sees each conversion shortly after it happens, with your own click ID, so you can match it to the click that brought the player in.

Owners, admins and marketers set postbacks up in STX Apps, under your affiliate’s Postbacks tab. Everyone in your organization can see them and their delivery log.

  1. Write your tracker’s URL with the macros you want filled in, for example:

    https://tracker.example/pb?cid={click_id}&e={event}&s={sub}&amt={amount}&ref={player_ref}&ts={ts}
  2. Pick the steps to send on.

  3. Save it, and copy the signing secret. It starts with pbsec_ and is shown once.

  4. Choose Send test postback to check your tracker receives it.

Your postback URL must be a public https address. Private and internal network addresses are refused, and redirects are not followed, so give the address your tracker answers on directly.

A new postback starts from the moment you save it. Conversions from before then are not sent.

Step {event} Sent when
Sign-up sign_up A player signs up through your link
Verified verified The player verifies their email
First deposit first_deposit The player deposits for the first time. {amount} is the deposit
First trade first_trade The player’s first trade

Each player reaches each step once, so each step is sent at most once per player and postback.

STX replaces each macro with its value, URL-encoded. A macro with no value is left empty, and anything else in braces is left as you typed it.

Macro Value Example
{click_id} The click_id on the link the player came through abc123
{sub} The sub-ID on that link nfl-wk7
{link} The link’s code ip7owude
{event} The step, from the table above first_deposit
{amount} The first deposit’s amount; empty for other steps 50.0000
{currency} The amount’s currency USD
{player_ref} The player’s anonymous reference, the same as in your Players report plr_q4z7d2k9m3x8a6bt
{ts} When the step happened, in Unix seconds 1792504991
{conversion_id} A unique ID for this conversion. Use it to ignore a repeat 1042

With the template above, a first deposit calls:

https://tracker.example/pb?cid=abc123&e=first_deposit&s=nfl-wk7&amt=50.0000&ref=plr_q4z7d2k9m3x8a6bt&ts=1792504991

Each postback is an HTTP GET of your URL, with these headers:

Header Value
x-stx-signature t=1792505021,v1=e721b0c9a20e20bdd6696199227ea5d585ad1bb4254aed67b323ecbcaec34826
x-stx-event The step, as in {event}
x-stx-conversion-id The conversion’s ID, as in {conversion_id}
user-agent STX-Apps-Postback/1

Answer with any 2xx within 5 seconds. Redirects are not followed.

x-stx-signature is t=<unix seconds>,v1=<signature>, where the signature is the hex HMAC-SHA256 of <t>.<url> under your postback’s secret, and <url> is the full URL STX called, exactly as sent. To check it:

  1. Rebuild the URL STX called: your postback’s scheme, host and path, then the query string exactly as it arrived, without decoding or reordering it.
  2. Compute the HMAC-SHA256 of t, a dot and that URL, with your secret as the key.
  3. Compare it with v1 in constant time, and reject a t more than a few minutes from your clock, so a captured call can’t be replayed later.

With the secret pbsec_3f9a1c7e5b2d8f4a6c0e9b1d3f5a7c9e2b4d, the call above signed at t=1792505021 gives v1=e721b0c9a20e20bdd6696199227ea5d585ad1bb4254aed67b323ecbcaec34826.

import hashlib
import hmac
import time
# base is your postback URL up to the "?", e.g. "https://tracker.example/pb".
# raw_query is the query string as received, before any decoding.
def verify_postback(secret: str, base: str, raw_query: str, header: str, tolerance: int = 300) -> bool:
parts = dict(p.split("=", 1) for p in header.split(",") if "=" in p)
try:
t = int(parts["t"])
except (KeyError, ValueError):
return False
if abs(time.time() - t) > tolerance:
return False
url = f"{base}?{raw_query}" if raw_query else base
expected = hmac.new(secret.encode(), f"{t}.{url}".encode(), hashlib.sha256).hexdigest()
return hmac.compare_digest(expected, parts.get("v1", ""))

A call that gets no 2xx is tried again after 1 minute, 5 minutes, 30 minutes, 2 hours and 6 hours, about 9 hours in all. If the last try fails too, the call is marked failed. Use {conversion_id} or the x-stx-conversion-id header to ignore a call you have already counted.

  • Held. A conversion from a player whose sign-up is under STX review (see Self-referrals) is held and never sent.
  • Paused. While a postback is paused, its conversions are skipped, not queued, and are not sent when you resume it.
  • Test. Send test postback calls your URL once, straight away, even while the postback is paused, with made-up values: {event} test, {click_id} test-click, {sub} test, {link} test, {amount} 0, {currency} USD and {player_ref} plr_testtesttesttest. It is signed like a real call and is not retried.

The Delivery log shows the latest 50 calls across your postbacks: when, which tracker, the step, the conversion, its status (delivered, pending, failed, held or skipped), your tracker’s HTTP response code, the number of tries and the last error. Times are UTC.