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.
Set one up
Section titled “Set one up”-
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} -
Pick the steps to send on.
-
Save it, and copy the signing secret. It starts with
pbsec_and is shown once. -
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.
Macros
Section titled “Macros”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=1792504991The call
Section titled “The call”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.
Check the signature
Section titled “Check the signature”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:
- 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.
- Compute the HMAC-SHA256 of
t, a dot and that URL, with your secret as the key. - Compare it with
v1in constant time, and reject atmore 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 hashlibimport hmacimport 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", ""))import crypto from 'node:crypto'
// base is your postback URL up to the '?', e.g. "https://tracker.example/pb".// rawQuery is the query string as received, e.g. req.originalUrl.split('?')[1].function verifyPostback(secret, base, rawQuery, header, toleranceSeconds = 300) { const parts = Object.fromEntries(header.split(',').map((p) => p.split('=', 2))) const t = Number(parts.t) if (!Number.isInteger(t) || Math.abs(Date.now() / 1000 - t) > toleranceSeconds) return false const url = rawQuery ? `${base}?${rawQuery}` : base const expected = crypto.createHmac('sha256', secret).update(`${t}.${url}`).digest('hex') const given = Buffer.from(parts.v1 ?? '', 'utf8') return given.length === expected.length && crypto.timingSafeEqual(given, Buffer.from(expected, 'utf8'))}Retries
Section titled “Retries”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, paused and test calls
Section titled “Held, paused and test calls”- 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}USDand{player_ref}plr_testtesttesttest. It is signed like a real call and is not retried.
Delivery log
Section titled “Delivery log”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.

