A reject reason says why a lead was not accepted. It turns a failed lead into information you can act on. Two parameters carry it, and they do different jobs:
reason_detailanswers what the advertiser actually said. Free text, passed through as it came, never aggregated.reasonanswers what kind of rejection this was. It is a short, repeated label, and it is what the reports group by.
Set both wherever you can. reason makes the rejection countable, reason_detail makes the individual lead readable.
Always set one
If no reason is defined, the lead falls into Error. Error means something broke, and it should stay that way. A lead the advertiser deliberately turned down is not a broken lead, and mixing the two makes both useless in reporting.
So map a reason for every rejection path in your integration. If the advertiser gives you nothing to work with, fall back to a general one such as Unspecified. Any reason is better than none.
Use the predefined reasons
You can write anything into reason, but pick from the standard list wherever it fits. Shared names are what make reporting comparable across advertisers, and the whole point of the parameter is that the same rejection looks the same everywhere.
A custom reason is worth creating only when it will repeat across many leads. Anything that is specific to one lead, or to one oddly worded response, belongs in reason_detail instead. Every new string in reason is a new row in the report, and a report with sixty rows of one lead each answers nothing.
Keep the names short
Reasons are aggregated in reports by name. Every distinct string becomes its own row.
That means Not Eligible is a useful line in a report, while We are not interested in this lead at the moment is a line that will never group with anything and will never tell you anything. Keep reasons to one or two words, in English, and reuse the same wording everywhere. The long version is exactly what reason_detail is for.
Standard reasons
Pick these from the picker rather than typing them, so the spelling is always identical. See Parameters. Phase says where the reason can realistically come from. Submit is the moment the lead is offered or posted to the advertiser. Postback is later, once the advertiser has worked with the lead. Several reasons appear in both, because an advertiser may find the same thing out immediately or a week later.
At submit, when the lead is sent to the advertiser:
- Cap: the advertiser’s daily or monthly limit is used up
- Outside Hours: outside the advertiser’s accepted hours
- Product Mismatch: the data does not fit the product, such as amount, term, type, region or segment
- Invalid Data: the advertiser refused the payload itself, such as a malformed or implausible value
At submit or by postback, since the advertiser may find these out immediately or later:
- Unspecified: the advertiser rejected the lead and gave no reason. We recommend using it as the default reject reason.
- Duplicate: the same application or order already exists at the advertiser
- Existing Customer: already a customer, or in arrears with the advertiser
- Not Eligible: fails a condition you could not check in advance
- Fraud: suspected fraud or a false identity
- Blacklist: the person is on the advertiser’s blocklist
- Invalid Traffic: bots, cookie stuffing, self-referrals, incentivised traffic
- Test: a test lead or order, not billed
- Age: outside the product’s age range
- Verification Failed: invalid document, unfinished BankID, missing paperwork
- Poor Credit: bad history, low score, negative record in a credit register
- Low Income: income below the threshold, or an unacceptable income type
- Too Much Debt: debt and repayments too high relative to income
- Insolvency: insolvency is in progress or has been filed
- Debt Collection: enforcement or debt collection against the person
- No Bank Account: no bank account, or it cannot be verified
- Out of Stock: the goods are unavailable and the order cannot be fulfilled
- Not Available: the service is not available at that location, such as coverage or connection
- Contract Locked: tied to the current supplier, outside the notice period
By postback only, once the advertiser has worked with the lead:
- Uncontactable: could not be reached, contact details do not work
- Cancelled by Customer: the person refused the offer or cancelled the order
- Cancelled by Advertiser: the advertiser cancelled it, technically, manually or commercially
- Expired: the offer was never opened and it expired
- Withdrawn: withdrawn after the contract, such as goods returned or contract cancelled
- Payment Failed: payment declined, card refused, proforma invoice unpaid
- Not Delivered: not delivered, not collected, the address does not exist
- Chargeback: payment reversed by the bank, which is a fraud signal rather than a withdrawal
Duplicate means two things
Duplicate is also a lead status, set by PalDock when the same origin arrives twice. See Origin Deduplication.
As a reject reason it means something different: PalDock accepted the lead as new, and the advertiser recognised it as one they already have. The status is about your side, the reason is about theirs.
Unspecified, not Not Eligible
The reject reason should reflect what the advertiser actually told us.
- Not Eligible means the advertiser named a condition the lead failed, and it was something you could not have checked in advance.
- Unspecified means the advertiser refused the lead without giving a reason. A flat refusal such as false, rejected, declined, or an empty error object is Unspecified. Every time.
This matters because Unspecified is worth checking from time to time. It is a catch-all bucket, so new advertiser-side errors or changes can show up there. It also gives us something to take back to the advertiser: these leads were refused, but no reason was provided.
The same rule applies to any specific reason. Map it only when the advertiser actually provided it, and use reasons from the standard list so they group consistently.
Technical errors fill themselves in
You do not have to map the advertiser’s error responses by hand. When a run reaches a failed End node with {reason} still empty and the last HTTP response was a 4xx or 5xx, PalDock fills both parameters for you:
reasonbecomesHTTP 500,HTTP 429, and so on, one value per status codereason_detailbecomes the status followed by the response body
Anything you set yourself wins. The automatic value is a fallback for the paths you did not cover, not something that overwrites a mapped reason.
Status codes make good report rows because there are only so many of them, and they group by themselves. A block of HTTP 429 is a rate limit to raise with the advertiser, a block of HTTP 400 is your payload, and both are visibly different from a lead the advertiser actually turned down.
Your own reasons
If nothing on the list fits, create your own. Two rules: keep it short, and write it exactly the same way every time. A typo makes a second row in the report that looks like a second reason.
Reasons typed by hand are not normalised, so partner declined and Partner Declined will be two rows. Predefined reasons are, which is the other argument for using them.
Where a reason is set
The same two parameters are used in both places, and there is only one set of values. A rejection that arrives by postback should carry the same Poor Credit as one decided at submit.
In a scenario
The reason is stored in the {reason} parameter, the detail in {reason_detail}. There are two ways to write into them.
Modify node. One node handles all your reasons, because you can define several conditions inside it. This is the recommended approach, otherwise you end up with twenty Set nodes and twenty connections just to cover the advertiser’s rejection responses.
Example, mapping one advertiser response to a standard reason:
- Title: Reject reason
- Field:
{reason} - Source:
parsedBody.status(the field in the advertiser’s response) - Operator: Equals
- Value:
This lead does not meet our criteria - Modification: Set
- Output:
Not Eligible
Add another section inside the same node for each further reason.
The detail is usually simpler, because it is a straight copy. One section with no condition, writing parsedBody.status (or whatever the advertiser’s message field is) into {reason_detail}, covers every path at once. That is worth doing even before you have mapped a single reason, since it is what tells you which responses to map next.
Set node. Also possible, and it writes into {reason} the same way. But you need a separate Set node for every reason, each reached by its own connection with a condition. Use it only when that reason needs its own branch in the flow anyway.
In a postback
An advertiser deciding later sends reason and reason_detail as parameters on the postback, alongside the identifier and the result. See Tracking parameters.
Incoming values are matched against the standard list case insensitively, ignoring spaces, underscores and hyphens, so payment_failed, PAYMENT FAILED and Payment Failed all store as Payment Failed. Anything not on the list is stored as it arrived, trimmed and otherwise untouched, because there is no way to know which capitalisation was meant.
That is the practical reason to hand your advertiser the standard list. Whatever they send from it will land in the right report row regardless of how they write it.
What the run reports
Every run ends in one of these, and the log tells you which. Three of them are not rejections at all, and knowing them apart is what stops you from hunting a bug that is not there.
- TIMED OUT: something ran out of time, either the node, the whole run, or the pingtree. See Limits and timeouts.
- BREAKER: a Breaker stopped a loop that had repeated as often as it was allowed to.
- DEAD END: the flow reached a node and no connection leading out of it matched, so it simply stopped. Nothing was written and no End node was reached.
- The reason from a failed End node: the flow finished properly and the value of
{reason}is reported. This is the only one that is a real rejection, and it is empty when you did not set a reason.
Dead End is the one to watch. It is the most common way an integration reports errors it does not really have, and it happens for a simple reason: most scenarios are built for the path where everything works. The advertiser then answers with something nobody planned for, no condition matches it, and the run stops mid-flow.
The fix is not clever conditions, it is coverage. Go through the advertiser’s documentation, find every response they say they can return, and give each one a connection that leads somewhere. Anything you cannot enumerate goes down a catch-all branch, matched with a negative pattern against the responses you do know, ending in a failed End node with Unspecified as the reason and the advertiser’s raw response in reason_detail.
That way a lead you did not anticipate still arrives as a rejection you can read, instead of as an error you have to investigate. The reason_detail on those Unspecified rows is your list of responses still to map.
Where the reasons show up
- Lead log, on the Not sold status. The detail lists each channel and why. See Logs.
- Pingtree reject reason report, aggregating what advertisers said at submit.
- Postback reject reason report, aggregating what they said afterwards.
The two reports are split because the questions are different. Rejections at submit are about whether you are sending the right leads to the right channel. Rejections by postback are about what happened to leads that were already bought, and they hit money that has already been counted.
Leads refused on arrival are elsewhere
None of this covers a lead PalDock never accepted: a missing required field, a failed validation, a lead outside the offer’s filters. Those never reach an advertiser, so no advertiser ever gave a reason for them.
They are in the lead log with their own status, and aggregated in the Affiliate Fails report. That report has its own set of causes, technical rather than commercial, and deliberately does not mix with the reasons on this page.

