Integration documentation
Webhooks
How Cashbanx notifies a partner about the life cycle of a purchase — from identification through to the cashback payment.
01How it works
Cashbanx tracks the purchase your user made at the partner store and notifies you at every
relevant change. There are four events, each with its own URL in your environment. We send a
POST; you respond.
- The user leaves your environment for the store through a Cashbanx link. That click is
recorded and carries the
userIdentifier— the user's identifier in your system. - The affiliate network reports the purchase to us. We match it to the click and send RECOGNIZED_PURCHASE.
- When the network confirms the purchase we send CONFIRMED_PURCHASE; if it cancels, we send CANCELED_PURCHASE.
- When the cashback withdrawal is paid, we send PAID_CASHBACK.
A canceled purchase receives neither a confirmation nor a payment. And an event with no URL configured is simply not sent — it is not queued waiting for you to configure it later.
02What we need from you
| Item | Description |
|---|---|
| RECOGNIZED_PURCHASE | URL of the endpoint that receives the identified purchase. |
| CONFIRMED_PURCHASE | URL of the endpoint that receives the confirmation. |
| CANCELED_PURCHASE | URL of the endpoint that receives the cancellation. |
| PAID_CASHBACK | URL of the endpoint that receives the withdrawal payment. |
| API key | A secret value we will send on every POST so you can confirm
the call is ours. Up to 255 characters. |
The URLs are independent: they may point to four distinct endpoints or to the same one — the
webhookType field in the body tells you which event arrived. We recommend HTTPS
on all of them.
Each environment has its own configuration. Staging URLs and keys are not the production ones.
03Authentication
The default is a static key in a header. Every POST leaving Cashbanx
carries:
x-api-key: <the key agreed with you>Reject any call that does not carry the correct key. It is the only factor identifying the origin — we do not sign the body and we send no timestamp; protection against third parties is the key plus TLS.
Variant: OAuth 2.0
For partners that require an access token, Cashbanx can obtain a token from your
authentication endpoint before each delivery and send the webhook with
Authorization: Bearer instead of x-api-key. In that arrangement we need
the login URL and the credentials, and we support two ways of presenting them:
x-www-form-urlencodedbody — we send the agreed parameters (for examplegrant_type,client_id,client_secret) in the body of the loginPOST.Authorization: Basic— we send the credential in the header, with an empty body.
In both cases we expect a JSON response containing an access_token field. This is
arranged case by case; the standard integration uses x-api-key only.
04The request
| Property | Value |
|---|---|
| Method | POST |
| Content-Type | application/json |
| Authentication | x-api-key |
| Timeout | 30 seconds |
| Redirects | followed automatically |
Thirty seconds without a response counts as a failure and enters the retry queue. If your
processing is slow, accept the notification, respond 200 and handle the body
asynchronously.
05The four events
| Event | When we send it | Note |
|---|---|---|
| RECOGNIZED_PURCHASE | The affiliate network reported the purchase and we identified it. | The purchase starts out pending. Nothing has been credited yet. |
| CONFIRMED_PURCHASE | The affiliate network confirmed the purchase. | End of the store's cancellation window. This is where cashback stops being provisional. |
| CANCELED_PURCHASE | The affiliate network canceled the purchase. | Final state. No further event is sent for that purchase. |
| PAID_CASHBACK | The cashback withdrawal changed to paid. | Final state of the financial cycle. |
06Payload
The body is always the same envelope: the event type and the transaction.
{
"webhookType": "RECOGNIZED_PURCHASE | CONFIRMED_PURCHASE | CANCELED_PURCHASE | PAID_CASHBACK",
"transaction": { }
}Examples per event
The purchase was identified and is pending. Nothing has been credited yet.
{
"webhookType": "RECOGNIZED_PURCHASE",
"transaction": {
"id": 23026,
"clickOrigin": "Android Mobile",
"voucherCode": "",
"status": "Pending",
"statusUpdateDate": null,
"type": "Identified",
"cashbackStatus": "Pending",
"amended": false,
"amendReason": "",
"createdDate": "2026-08-14T07:12:25.000Z",
"updatedDate": null,
"transactionDate": "2026-08-13T23:59:00.000Z",
"paidDate": null,
"thirdPartyId": "1527912405",
"purchaseOrderNumber": "16181113",
"paymentOrderNumber": null,
"amount": 350,
"oldAmount": null,
"currency": "BRL",
"sourceAmount": null,
"sourceCurrency": "",
"quotationValue": null,
"oldQuotationValue": null,
"commission": 17.5,
"oldCommission": null,
"commissionPercentage": 0.05,
"commissionReceiptDate": null,
"cashbackValue": 10.5,
"oldCashbackValue": null,
"cashbackPercentage": 0.03,
"cashbackPoints": 350,
"taxValue": 1.75,
"partnerValue": 0,
"companyValue": 5.25,
"paidPartnerValue": false,
"paymentDatePartnerValue": null,
"productBasket": null,
"photo": "",
"store": {
"id": 3176,
"name": "Nike",
"logo": "https://cashbanx.s3.amazonaws.com/retangle-logos/nike.png",
"circleLogo": "https://cashbanx.s3.amazonaws.com/circle-logos/nike.png"
},
"user": {
"id": 8842,
"userIdentifier": "3c217db6-fa75-4252-94dd-af8e10b77454",
"name": "Maria Souza"
},
"goOut": {
"id": 1251,
"additionalProps": "mothers-day-campaign",
"date": "2026-08-13 09:56:47",
"userAgent": "Mozilla/5.0 (iPhone; CPU iPhone OS 18_3_2 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Mobile/15E148"
}
}
}The network confirmed. Note statusUpdateDate, updatedDate and the withdraw object, none of which existed in the previous event.
{
"webhookType": "CONFIRMED_PURCHASE",
"transaction": {
"id": 23026,
"clickOrigin": "Android Mobile",
"voucherCode": "",
"status": "Approved",
"statusUpdateDate": "2026-09-13T04:00:11.000Z",
"type": "Identified",
"cashbackStatus": "Pending",
"amended": false,
"amendReason": "",
"createdDate": "2026-08-14T07:12:25.000Z",
"updatedDate": "2026-09-13T04:00:11.000Z",
"transactionDate": "2026-08-13T23:59:00.000Z",
"paidDate": null,
"thirdPartyId": "1527912405",
"purchaseOrderNumber": "16181113",
"paymentOrderNumber": null,
"amount": 350,
"oldAmount": null,
"currency": "BRL",
"sourceAmount": null,
"sourceCurrency": "",
"quotationValue": null,
"oldQuotationValue": null,
"commission": 17.5,
"oldCommission": null,
"commissionPercentage": 0.05,
"commissionReceiptDate": "2026-09-13T04:00:11.000Z",
"cashbackValue": 10.5,
"oldCashbackValue": null,
"cashbackPercentage": 0.03,
"cashbackPoints": 350,
"taxValue": 1.75,
"partnerValue": 0,
"companyValue": 5.25,
"paidPartnerValue": false,
"paymentDatePartnerValue": null,
"productBasket": null,
"photo": "",
"store": {
"id": 3176,
"name": "Nike",
"logo": "https://cashbanx.s3.amazonaws.com/retangle-logos/nike.png",
"circleLogo": "https://cashbanx.s3.amazonaws.com/circle-logos/nike.png"
},
"user": {
"id": 8842,
"userIdentifier": "3c217db6-fa75-4252-94dd-af8e10b77454",
"name": "Maria Souza"
},
"goOut": {
"id": 1251,
"additionalProps": "mothers-day-campaign",
"date": "2026-08-13 09:56:47",
"userAgent": "Mozilla/5.0 (iPhone; CPU iPhone OS 18_3_2 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Mobile/15E148"
},
"withdraw": {
"id": 4821,
"status": "Pending"
}
}
}The network canceled and changed the amounts: the old… fields hold what they were worth before, and the current ones go to zero. There is no withdraw.
{
"webhookType": "CANCELED_PURCHASE",
"transaction": {
"id": 23026,
"clickOrigin": "Android Mobile",
"voucherCode": "",
"status": "Declined",
"statusUpdateDate": "2026-09-02T11:20:03.000Z",
"type": "Identified",
"cashbackStatus": "Canceled",
"amended": true,
"amendReason": "Order canceled by the store",
"createdDate": "2026-08-14T07:12:25.000Z",
"updatedDate": "2026-09-02T11:20:03.000Z",
"transactionDate": "2026-08-13T23:59:00.000Z",
"paidDate": null,
"thirdPartyId": "1527912405",
"purchaseOrderNumber": "16181113",
"paymentOrderNumber": null,
"amount": 0,
"oldAmount": 350,
"currency": "BRL",
"sourceAmount": null,
"sourceCurrency": "",
"quotationValue": null,
"oldQuotationValue": null,
"commission": 0,
"oldCommission": 17.5,
"commissionPercentage": 0.05,
"commissionReceiptDate": null,
"cashbackValue": 0,
"oldCashbackValue": 10.5,
"cashbackPercentage": 0.03,
"cashbackPoints": 0,
"taxValue": 0,
"partnerValue": 0,
"companyValue": 0,
"paidPartnerValue": false,
"paymentDatePartnerValue": null,
"productBasket": null,
"photo": "",
"store": {
"id": 3176,
"name": "Nike",
"logo": "https://cashbanx.s3.amazonaws.com/retangle-logos/nike.png",
"circleLogo": "https://cashbanx.s3.amazonaws.com/circle-logos/nike.png"
},
"user": {
"id": 8842,
"userIdentifier": "3c217db6-fa75-4252-94dd-af8e10b77454",
"name": "Maria Souza"
},
"goOut": {
"id": 1251,
"additionalProps": "mothers-day-campaign",
"date": "2026-08-13 09:56:47",
"userAgent": "Mozilla/5.0 (iPhone; CPU iPhone OS 18_3_2 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Mobile/15E148"
}
}
}The withdrawal was paid. paidDate filled in, cashbackStatus at Finished and withdraw.status at Confirmed.
{
"webhookType": "PAID_CASHBACK",
"transaction": {
"id": 23026,
"clickOrigin": "Android Mobile",
"voucherCode": "",
"status": "Approved",
"statusUpdateDate": "2026-09-13T04:00:11.000Z",
"type": "Identified",
"cashbackStatus": "Finished",
"amended": false,
"amendReason": "",
"createdDate": "2026-08-14T07:12:25.000Z",
"updatedDate": "2026-09-15T23:59:00.000Z",
"transactionDate": "2026-08-13T23:59:00.000Z",
"paidDate": "2026-09-15T23:59:00.000Z",
"thirdPartyId": "1527912405",
"purchaseOrderNumber": "16181113",
"paymentOrderNumber": "PO-2026-09-4471",
"amount": 350,
"oldAmount": null,
"currency": "BRL",
"sourceAmount": null,
"sourceCurrency": "",
"quotationValue": null,
"oldQuotationValue": null,
"commission": 17.5,
"oldCommission": null,
"commissionPercentage": 0.05,
"commissionReceiptDate": "2026-09-13T04:00:11.000Z",
"cashbackValue": 10.5,
"oldCashbackValue": null,
"cashbackPercentage": 0.03,
"cashbackPoints": 350,
"taxValue": 1.75,
"partnerValue": 0,
"companyValue": 5.25,
"paidPartnerValue": false,
"paymentDatePartnerValue": null,
"productBasket": null,
"photo": "",
"store": {
"id": 3176,
"name": "Nike",
"logo": "https://cashbanx.s3.amazonaws.com/retangle-logos/nike.png",
"circleLogo": "https://cashbanx.s3.amazonaws.com/circle-logos/nike.png"
},
"user": {
"id": 8842,
"userIdentifier": "3c217db6-fa75-4252-94dd-af8e10b77454",
"name": "Maria Souza"
},
"goOut": {
"id": 1251,
"additionalProps": "mothers-day-campaign",
"date": "2026-08-13 09:56:47",
"userAgent": "Mozilla/5.0 (iPhone; CPU iPhone OS 18_3_2 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Mobile/15E148"
},
"withdraw": {
"id": 4821,
"status": "Confirmed"
}
}
}Field dictionary
The presence column says what you can count on. There are three cases, and the difference between them is what decides whether your code needs a guard:
- always — the key is in every delivery, with a useful value. Safe to read directly.
- may be empty — the key is always there, but the
value may be
null(numeric and date fields) or an empty string (text fields). It never goes missing; it may say nothing. - may be missing — the key may not exist in the JSON. Reading it without a guard breaks.
The rule behind it: a text field with no value arrives as "", a numeric or date
field with no value arrives as null, and whatever does not apply to that delivery
disappears rather than arriving null.
| Field | Presence | Description |
|---|---|---|
| webhookType | always | Which event arrived. One of the four values in section 05. |
| transaction.id | always | Purchase identifier at Cashbanx. Stable across every event — this is your reconciliation key. |
| transaction.status | always | Purchase state. |
| transaction.type | always | How the purchase was attributed to the user. |
| transaction.cashbackStatus | always | Cashback state. |
| transaction.createdDate | always | When the purchase was created at Cashbanx. |
| transaction.transactionDate | always | When the purchase happened at the store. |
| transaction.statusUpdateDate | may be empty | Date of the last state change. null while the purchase has not changed state. |
| transaction.updatedDate | may be empty | When the purchase was updated at Cashbanx. null if it never was. |
| transaction.paidDate | may be empty | When the cashback was paid. Only carries a value in PAID_CASHBACK. |
| transaction.clickOrigin | may be empty | Device the click came from. Empty string when the network does not report it. |
| transaction.voucherCode | may be empty | Coupon code used. Empty string on most purchases. |
| transaction.amended | may be empty | true if the affiliate network changed the purchase after reporting it. |
| transaction.amendReason | may be empty | Reason for the change. Empty string when amended is false. |
| transaction.thirdPartyId | may be empty | Purchase identifier at the affiliate network. |
| transaction.purchaseOrderNumber | may be empty | Order number at the store. |
| transaction.paymentOrderNumber | may be empty | Payment order number at the affiliate network. Usually only arrives after payment. |
| transaction.amount | may be empty | Total purchase amount. Can be null — the column accepts null and some networks do not report the amount. |
| transaction.oldAmount | may be empty | Previous amount, when the purchase was changed. null if it was never changed. |
| transaction.currency | may be empty | Purchase currency. |
| transaction.sourceAmount | may be empty | Amount in the source currency, for a converted purchase. |
| transaction.sourceCurrency | may be empty | Source currency, for a converted purchase. |
| transaction.quotationValue | may be empty | Exchange rate applied in the conversion. |
| transaction.oldQuotationValue | may be empty | Previous exchange rate, when it was revised. |
| transaction.commission | may be empty | Affiliate network commission on the purchase. |
| transaction.oldCommission | may be empty | Previous commission, when changed. |
| transaction.commissionPercentage | always | Commission rate, as a fraction (0.05 = 5%). |
| transaction.commissionReceiptDate | may be empty | When the commission was received from the affiliate network. |
| transaction.cashbackValue | always | The user's cashback amount. Goes to zero on a canceled purchase. |
| transaction.oldCashbackValue | may be empty | Previous cashback, when changed. This is where the original amount of a canceled purchase ends up. |
| transaction.cashbackPercentage | always | Cashback rate, as a fraction. |
| transaction.cashbackPoints | may be missing | The same cashback converted into points. Only present for partners whose programme runs on points; for the others the key does not appear. |
| transaction.taxValue | always | Taxes withheld on the commission. |
| transaction.partnerValue | always | Share of the commission owed to the partner. |
| transaction.companyValue | always | Share of the commission retained by Cashbanx. |
| transaction.paidPartnerValue | always | true if the partner's share has already been paid out. |
| transaction.paymentDatePartnerValue | may be empty | Date of the payout to the partner. |
| transaction.productBasket | may be empty | Purchase items. null for most networks, which do not send them. |
| transaction.photo | may be empty | Receipt URL. Empty string when there is none. |
| store | always | The store object. Arrives as {} on a purchase with no associated click. |
| store.id | may be missing | Store identifier in your programme's catalogue — the same id the store endpoints of the API return, not a global id. |
| store.name | may be missing | Store name. |
| store.logo | may be missing | Rectangular logo URL. |
| store.circleLogo | may be missing | Circular logo URL. |
| user | always | The user object. Arrives as {} on a purchase with no associated click. |
| user.userIdentifier | may be missing | The user's identifier in your system, exactly as you sent it on the click. This is how you recognise whose purchase it is. May be an empty string on an unidentified purchase. |
| user.id | may be missing | User identifier at Cashbanx. Absent if the user is not registered on our side. |
| user.name | may be missing | User name at Cashbanx. Absent for the same reason. |
| goOut | always | The click object. Arrives as {} on a purchase with no associated click. |
| goOut.id | may be missing | Identifier of the click that originated the purchase. |
| goOut.date | may be missing | Click date and time. Format YYYY-MM-DD HH:MM:SS, not ISO. |
| goOut.userAgent | may be missing | Browser user agent at the moment of the click. |
| goOut.additionalProps | may be missing | Free text you sent on the click and we return here. Useful for campaign, source or any correlation of your own. |
| withdraw.id | may be missing | Withdrawal identifier. The withdraw key only exists in CONFIRMED_PURCHASE and PAID_CASHBACK. |
| withdraw.status | may be missing | Withdrawal state. |
No field with that name.
null
When a value does not apply — cashbackPoints on a cashback programme,
withdraw on a purchase with no withdrawal, user.id for a user we have
no record of — the key disappears from the JSON. Treat absence and null as
equivalent, and do not depend on any optional key being present.
For the same reason your parser must ignore fields it does not know: new fields may appear without notice, and that is not considered a breaking change.
Enum domains
Every enum travels by name, as text — never as a number.
| Field | Possible values |
|---|---|
| transaction.status | Pending, Approved, Declined, Received |
| transaction.type | Unidentified, Identified, NoAssignment |
| transaction.cashbackStatus | Pending, Ahead, Reversed, Finished, Canceled, Analyzing |
| withdraw.status | Pending, Confirmed, Disapproved, Waiting |
Dates follow ISO 8601 in UTC (2026-08-14T07:12:25.000Z). One inherited
exception: goOut.date arrives as YYYY-MM-DD HH:MM:SS.
07Expected values per event
The values are not free — it is the state of the purchase that decides which webhook goes out. Use this matrix to validate what you receive:
| Event | status | cashbackStatus | withdraw.status |
|---|---|---|---|
| RECOGNIZED_PURCHASE | Pending | Pending, Analyzing, Ahead | absent |
| CONFIRMED_PURCHASE | Approved | Pending, Analyzing, Ahead | Pending or Waiting |
| CANCELED_PURCHASE | Declined | Canceled | absent |
| PAID_CASHBACK | Approved or Received | Finished | Confirmed |
In CONFIRMED_PURCHASE, the withdraw object appears when a withdrawal
already exists for the purchase — which depends on how your programme is configured. If a
withdrawal was created and moved before the confirmation, withdraw.status reflects its
real state at that moment and may carry another value from the domain.
08Your response
What decides the fate of the notification is the HTTP status code you return. The body of your response is not interpreted.
| You respond | We record | What happens |
|---|---|---|
| 200–299 | SENT | Delivered. We will not send this event again for this purchase. |
| 422 | CANCELED | Received and refused by your decision. We do not retry, and we do not send it again. Use it when you do not want this event for this purchase. |
| others | FAILED | Any other code, a timeout or a network error. Enters the retry queue. |
200
Responding 200 ends the notification for good. If processing failed on your side
and you want another attempt, respond with an error code — 500, for example. And
reserve 422 for a deliberate refusal, because it is final too.
09Retries
A notification that failed is retried up to 3 times, with growing intervals:
| Attempt | Approximate interval | Cumulative |
|---|---|---|
| 1st retry | ~5 minutes | ~5 min after the failure |
| 2nd retry | ~15 minutes | ~20 min |
| 3rd retry | ~45 minutes | ~1 h 05 min |
The intervals carry a random variation of up to 20% either way, so that an outage which knocked down many notifications at once does not send them all back at the same instant. Once the three attempts are exhausted, we stop.
A recovery job resumes retries left pending by an interruption on our side. Because of it, a notification may arrive hours after the original failure — which reinforces the point of the next section.
10Idempotency and ordering
- Deduplicate on
webhookType+transaction.id. That pair identifies the notification. We deliver each of them successfully only once per purchase, but a response lost in transit can make us resend something you have already processed. - A retry rebuilds the body from scratch. It reflects the state of the purchase at
the moment of the retry, not at the first attempt. Values such as
cashbackStatusorwithdraw.statusmay arrive different from what they would have been — and the second version is the correct one. - We do not guarantee ordering. A retried
CONFIRMED_PURCHASEmay arrive after aPAID_CASHBACK. Decide fromtransaction.status,cashbackStatusand the dates, never from the order in which the requests arrived.
11Delivery with side effects
For some programmes — agreed case by case, and stated in your integration contract — a
successful response to CONFIRMED_PURCHASE means that the partner has already
credited the user. In that arrangement Cashbanx marks the withdrawal as paid at that very
moment, and PAID_CASHBACK follows.
If this is your case, respond 2xx to CONFIRMED_PURCHASE
only after the credit is effective on your side. An optimistic 200 closes the
payment at Cashbanx without the user having received anything.
12Support
Questions about the integration, a URL change, a key rotation or a manual resend of a
notification: talk to your commercial contact at Cashbanx or write to
suporte@cashbanx.com, quoting the
transaction.id and the webhookType involved.