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
event | When | amount |
|---|---|---|
complete | A complete was booked for your organization (usually within 10 minutes) | The CPI shown to you in the surveys list for this offer (USD) |
screenout | The panelist did not qualify | 0, plus reason: overquota, qualification_mismatch or security |
reversal | The client reversed the complete after review | Exactly the negative of the complete amount |
reinstatement | The client undid a reversal | Exactly the complete amount again |
test | You pressed Send test postback | 0, 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"}| Field | Meaning |
|---|---|
eventId | Unique per event: <entryLinkCode>:<n> (:0 = complete, :1, :2 … = later reversals / reinstatements) or <entryLinkCode>:screenout. De-duplicate on it. |
entryLinkCode | The same code as on the redirect — your transaction key |
panelistId, subId1–subId3, surveyId | As sent / echoed on the redirect |
amount, currency | Net amount in USD (negative for reversal) |
occurredAt | When 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=b4826e56e806125dac0e51333b087340371f4a3085a7150eb4155d728bcc5fb5Node.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
eventIdsuccessfully 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
sentAtwithin ± 10 minutes of your clock, and treat an unknowneventIdin areversalas informational (for example a complete from before you switched postbacks on).
Checklist
- Signature checked on the raw body
- Each
eventIdprocessed at most once -
reversaldeducts exactlyamount(negative) from the user,reinstatementcredits it again -
testevents never credited