Publisher API

Postbacks (server-to-server)

Receive a signed server-to-server call for every complete, screen-out, reversal and reinstatement — credit your users on the postback.

Postbacks are being rolled out. The settings appear under Apps → your app → Integration → Server-to-server postback once they are available for your app.

Why postbacks

The redirect tells your app where the panelist lands. A postback is a call from mypinio's server to your server — it also arrives when the panelist closes the browser, and it is the only channel that reports client reversals after the survey. Recommended rule: credit on the postback, use the redirect for display.

Events

eventWhenamount
completeA complete was booked for your organization (usually within 10 minutes)The CPI shown to you in the surveys list for this offer (USD)
screenoutThe panelist did not qualify0, plus reason: overquota, qualification_mismatch or security
reversalThe client reversed the complete after reviewExactly the negative of the complete amount
reinstatementThe client undid a reversalExactly the complete amount again
testYou pressed Send test postback0, plus "test": true — never credit it

Choose the events per app. Reversals and reinstatements are also listed in the portal (Payments → Reversals, CSV) and via GET /api/v1/publishers/reversals.

Request

POST to your postback URL (HTTPS), Content-Type: application/json:

{"eventId":"AB12CD34:0","event":"complete","appId":"app_demo_123","panelistId":"user_abc123","entryLinkCode":"AB12CD34","surveyId":"12345","subId1":"click-1","subId2":null,"subId3":null,"amount":1.2345,"currency":"USD","occurredAt":"2026-10-19T10:00:00.000Z","sentAt":"2026-10-19T10:05:00.000Z"}
FieldMeaning
eventIdUnique per event: <entryLinkCode>:<n> (:0 = complete, :1, :2 … = later reversals / reinstatements) or <entryLinkCode>:screenout. De-duplicate on it.
entryLinkCodeThe same code as on the redirect — your transaction key
panelistId, subId1–subId3, surveyIdAs sent / echoed on the redirect
amount, currencyNet amount in USD (negative for reversal)
occurredAtWhen the event happened; sentAt when this request was sent

Headers: X-Mypinio-Signature: v1=<hex> and X-Mypinio-Event-Id.

Verify the signature

v1 = HMAC-SHA256 of the raw request body with your app's redirect signing secret (the same secret as for redirects; renewing it in the app settings renews both). Compare in constant time before parsing the JSON.

Test vector

Secret: example-signing-secret-0123456789, body exactly as in the request example above:

expected signature: v1=b4826e56e806125dac0e51333b087340371f4a3085a7150eb4155d728bcc5fb5

Node.js

import crypto from 'node:crypto'

export function verifyMypinioPostback(rawBody, signatureHeader, secret) {
  const expected = 'v1=' + crypto.createHmac('sha256', secret).update(rawBody, 'utf8').digest('hex')
  const a = Buffer.from(expected)
  const b = Buffer.from(String(signatureHeader ?? ''))
  return a.length === b.length && crypto.timingSafeEqual(a, b)
}

Python

import hashlib, hmac

def verify_mypinio_postback(raw_body: bytes, signature_header: str, secret: str) -> bool:
    expected = "v1=" + hmac.new(secret.encode(), raw_body, hashlib.sha256).hexdigest()
    return hmac.compare_digest(expected, signature_header or "")

PHP

function verify_mypinio_postback(string $rawBody, string $signatureHeader, string $secret): bool {
    return hash_equals('v1=' . hash_hmac('sha256', $rawBody, $secret), $signatureHeader);
}

Answer and retries

  • Answer 2xx once you have stored the event — that is a delivery. mypinio delivers each eventId successfully at most once.
  • 5xx, 408, 425, 429 or no connection → automatic retries with growing pauses (about 2 hours in total). Other 4xx are final.
  • Redirects are not followed — the URL must answer directly.
  • Recommended: accept sentAt within ± 10 minutes of your clock, and treat an unknown eventId in a reversal as informational (for example a complete from before you switched postbacks on).

Checklist

  • Signature checked on the raw body
  • Each eventId processed at most once
  • reversal deducts exactly amount (negative) from the user, reinstatement credits it again
  • test events never credited

On this page