![]()
Outcome reporting • Recovery learning loop
For merchants, developers, and integration engineers
Version 1.0 | Source baseline: zahlen_deploy_0616A.tar.gz | June 2026
Core outcome principle |
A retry decision is a recommendation. A retry outcome is the observed result. Zahlen becomes more useful only when merchants report what actually happened after the scheduled attempt. |
Canonical Zahlen retry schedule
→ Day 2
→ Day 6
→ Day 16
Learning objectives |
By the end of this chapter, you should be able to submit accurate retry outcomes, correlate outcomes to decisions, preserve idempotency, and explain how outcome data closes Zahlen’s recovery learning loop. |
Why outcome reporting matters
The Retry Outcome API records the result of an authorization attempt after Zahlen has produced a retry decision or after the merchant has executed a scheduled retry. The endpoint is:
POST /v1/retry-outcome
Without outcome reporting, the platform can recommend an action but cannot verify whether the action recovered revenue, produced another decline, or resulted in a neutral operational result.
Outcome data changes Zahlen from a one-way recommendation service into a measurable recovery system.
Stage | Question answered | Durable evidence |
Payment Event | What happened at the original authorization? | Event ID, decline evidence, issuer and processor context |
Retry Decision | Should another attempt occur, and on which fixed schedule day? | Request ID, decision ID, retry day, reason and confidence |
Retry Outcome | What actually happened when the attempt was executed? | Outcome ID, outcome, approval or final decline evidence |
Recovery Intelligence | Did the schedule and policy recover revenue over time? | Observed recovery truth, trends, issuer behavior and reporting |
Do not infer success from scheduling |
A scheduled retry is not a successful payment. Report success only from observed authorization or settlement evidence. |
The outcome-reporting workflow
Preserve the request_id and decision_id returned by the Retry Decision API.
Execute the payment attempt in the merchant payment stack on the applicable fixed retry day: Day 1, Day 2, Day 6, or Day 16.
Capture the processor result, timestamp, approval code or final decline code, and settled amount when available.
Map the processor result into the documented RetryOutcomePayload.
Submit the outcome using the merchant X-API-Key and safe replay controls.
Persist the returned outcome_id and matched_by value with the merchant authorization audit trail.
Monitor missing or delayed outcomes so the recovery learning loop remains complete.
Two different kinds of retry |
An HTTP retry repeats an API request after a network or server failure. A payment retry performs another authorization attempt. HTTP retries must never create additional payment attempts or alter Zahlen’s fixed Day 1, Day 2, Day 6, and Day 16 schedule. |
Request contract: RetryOutcomePayload
The request model forbids unknown top-level properties. Only attempt_number and outcome are required by the schema, but correlation and processor evidence fields are operationally important. Tenant ownership is resolved from the authenticated API key, not trusted from the request body.
Field | Type | Required | Meaning and guidance |
merchant_id | string or null | No | Optional business identifier; authenticated tenant context remains authoritative. |
attempt_number | integer | Yes | Minimum 0. Use the attempt number that actually executed. |
outcome | string | Yes | Observed result. Use the deployment-approved outcome vocabulary. |
request_id | string or null | No | Correlates the outcome to the originating decision request. |
decision_id | string or null | No | Correlates the outcome to the specific Zahlen decision. |
token | string or null | No | Merchant payment token or safe payment reference; never a raw PAN. |
approval_code | string or null | No | Processor approval evidence when the attempt succeeds. |
final_decline_code | string or null | No | Final processor decline code when the attempt fails. |
settled_amount_minor | integer or null | No | Non-negative amount in minor units, such as cents. |
currency | string or null | No | Currency associated with settled_amount_minor. |
Field | Type | Required | Meaning and guidance |
outcome_timestamp | string or null | No | Actual event time, preferably ISO 8601 with timezone. |
Outcome vocabulary is deployment-specific |
The schema requires a string but does not define a universal enumeration. Use only outcome values documented by the active Zahlen deployment or onboarding contract. Do not invent client-only values without agreement. |
Submit a successful outcome
The following example reports that attempt 2, executed on Day 2 of the fixed schedule, was approved and settled for $29.99 USD.
curl -sS -X POST https://api.example.com/v1/retry-outcome \
-H 'Content-Type: application/json' \
-H 'X-API-Key: zk_live_REPLACE_ME' \
-H 'Idempotency-Key: outcome-dec_01-attempt-2' \
-d '{
"attempt_number": 2, "outcome": "RECOVERED", "request_id": "req_01", "decision_id": "dec_01", "token": "tok_customer_001", "approval_code": "APPR42", "settled_amount_minor": 2999, "currency": "USD",
"outcome_timestamp": "2026-06-17T15:42:18Z"
}'
Example values are illustrative |
Confirm the accepted outcome vocabulary and whether Idempotency-Key is enabled for the active deployment before production use. |
Submit a declined outcome
When a scheduled attempt is declined, report the observed decline rather than omitting the outcome. Failed attempts are essential evidence for evaluating later retry days and issuer patterns.
{
"attempt_number": 3, "outcome": "DECLINED", "request_id": "req_02", "decision_id": "dec_02", "token": "tok_customer_002",
"final_decline_code": "51", "currency": "USD",
"outcome_timestamp": "2026-06-21T10:03:00Z"
}
In this example, attempt 3 corresponds to the Day 6 position in Zahlen’s fixed schedule. A later attempt must still follow the next permitted schedule position rather than being triggered by an API transport retry.
Response contract: RetryOutcomeResponse
A successful response confirms the durable outcome record and returns the matching information Zahlen used to associate it with prior evidence.
Field | Type | How to use it |
outcome_id | string | Durable identifier for the recorded outcome. |
merchant_id | string | Merchant resolved by the authenticated context. |
request_id | string or null | Returned correlation to the decision request. |
decision_id | string or null | Returned correlation to the decision. |
token | string or null | Returned safe payment reference. |
attempt_number | integer | Recorded attempt number. |
outcome | string | Recorded outcome value. |
approval_code | string or null | Recorded approval evidence. |
final_decline_code | string or null | Recorded decline evidence. |
settled_amount_minor | integer or null | Recorded settled amount in minor units. |
currency | string or null | Recorded currency. |
outcome_timestamp | string or null | Time the merchant says the outcome occurred. |
created_at | string | Time Zahlen created the durable record. |
matched_by | string | How Zahlen correlated the outcome to existing evidence. |
{
"outcome_id": "out_01", "merchant_id": "merchant_demo", "request_id": "req_01", "decision_id": "dec_01",
"token": "tok_customer_001",
"attempt_number": 2, "outcome": "RECOVERED", "approval_code": "APPR42", "final_decline_code": null, "settled_amount_minor": 2999, "currency": "USD",
"outcome_timestamp": "2026-06-17T15:42:18Z", "created_at": "2026-06-17T15:42:20Z",
"matched_by": "decision_id"
}
Correlation and matching
Strong correlation allows Zahlen to connect a real processor result to the exact recommendation and payment context that produced it. Supply the strongest available identifiers rather than relying on a token alone.
Correlation field | Strength | Guidance |
decision_id | Strongest | Preferred direct link to one Zahlen decision. |
request_id | Strong | Links the outcome to the decision request and support trace. |
token + attempt_number | Fallback | Useful when decision identifiers are unavailable; requires careful token hygiene. |
merchant_id | Context only | Business context; not a substitute for tenant authentication or decision correlation. |
The response field matched_by explains how the server linked the outcome. Persist it for diagnostics and alert when production traffic unexpectedly falls back to a weaker matching method.
Store matched_by
The recovery learning loop
Outcome records are the feedback signal that lets Zahlen measure the effectiveness of the fixed retry schedule and distinguish recommendations from observed recovery. The loop operates at several levels:
Level | Outcome evidence enables |
Individual payment | Confirm whether the specific retry recovered or remained declined. |
Retry-day performance | Compare observed results at Day 1, Day 2, Day 6, and Day |
Level | Outcome evidence enables |
14. | |
Decline category | Measure which categories respond to later attempts and which remain unrecoverable. |
Issuer and BIN | Identify issuer-level recovery shifts, degradation, and timing patterns. |
Merchant program | Measure recovery rate, reporting completeness, and revenue recovered. |
Operational intelligence | Populate Recovery Truth, trends, monitoring events, issuer health, and investigation reporting. |
A complete loop does not mean the decision engine changes the fixed retry days. It means the platform can measure and explain performance within the canonical Day 1, Day 2, Day 6, and Day 16 framework and surface operational patterns that require attention.
Data-quality rules
Report the actual authorization or settlement result, not a prediction or scheduled state.
Use the actual outcome timestamp, not the time a reconciliation report was generated.
Keep attempt_number aligned with the executed attempt and fixed retry position.
Use settled_amount_minor only with the corresponding currency and non-negative integer units.
Send approval_code for observed approvals and final_decline_code for observed declines when available.
Use payment tokens or safe references; never submit a full PAN, CVV, password, or raw banking credential.
Preserve request_id, decision_id, outcome_id, and matched_by in logs and audit records.
Track the percentage of executed attempts that receive a durable outcome. Missing outcomes create biased recovery metrics and weaken issuer and retry-day analysis.
Reporting completeness is a production metric
Do not silently transform unknown processor results into RECOVERED or DECLINED; use an approved mapping table.
Safe API retries and duplicate prevention
Outcome submission can fail after the server has accepted a request but before the client receives the response. Design the client so repeating the same logical report does not create conflicting evidence.
Create one stable idempotency key for one logical outcome report when the deployment supports the header.
Reuse the same request body and idempotency key after a timeout or transient 5xx response.
Never change outcome, amount, or identifiers while reusing an idempotency key.
Apply bounded exponential backoff with jitter for 429 and transient server failures.
Do not retry validation failures until the payload is corrected.
If the final submission status is unknown, reconcile by identifiers or contact the approved support path before sending a contradictory outcome.
# Conceptual retry delay base_delay = 1.0
max_delay = 30.0
delay = min(max_delay, base_delay * (2 ** retry_number)) delay *= random.uniform(0.75, 1.25)
Error handling
HTTP status | Meaning | Client action |
200 / 201 | Outcome accepted | Persist outcome_id, matched_by, and correlation fields. |
400 | Malformed or business-invalid outcome | Correct mapping or required business data; do not blindly retry. |
401 | Missing or invalid API key | Check secret, environment, and X-API-Key header. |
403 | Authenticated but not permitted | Check plan, capability, or endpoint authorization. |
409 | Conflict or idempotency mismatch | Compare the original payload and idempotency key. |
422 | Schema validation failure | Fix field names and types; extra fields are forbidden. |
429 | Rate or quota enforcement | Honor Retry-After when present and back off with jitter. |
500 / 503 | Transient server or dependency failure | Retry safely using stable identifiers and idempotency; alert if sustained. |
Python example
import os import requests
base_url = os.environ["ZAHLEN_BASE_URL"] api_key = os.environ["ZAHLEN_API_KEY"]
payload = { "attempt_number": 2, "outcome": "RECOVERED", "request_id": "req_01", "decision_id": "dec_01",
"token": "tok_customer_001", "approval_code": "APPR42", "settled_amount_minor": 2999,
"currency": "USD",
"outcome_timestamp": "2026-06-17T15:42:18Z",
}
response = requests.post( f"{base_url}/v1/retry-outcome", headers={
"Content-Type": "application/json", "X-API-Key": api_key,
"Idempotency-Key": "outcome-dec_01-attempt-2",
},
json=payload, timeout=20,
)
response.raise_for_status() result = response.json()
print(result["outcome_id"], result["matched_by"])
JavaScript example
const payload = { attempt_number: 2, outcome: "RECOVERED", request_id: "req_01", decision_id: "dec_01", token: "tok_customer_001", approval_code: "APPR42",
settled_amount_minor: 2999, currency: "USD",
outcome_timestamp: "2026-06-17T15:42:18Z"
};
const response = await fetch(
`${process.env.ZAHLEN_BASE_URL}/v1/retry-outcome`,
{
method: "POST", headers: {
"Content-Type": "application/json",
"X-API-Key": process.env.ZAHLEN_API_KEY, "Idempotency-Key": "outcome-dec_01-attempt-2"
},
body: JSON.stringify(payload)
}
);
if (!response.ok) {
throw new Error(`Zahlen HTTP ${response.status}`);
}
const result = await response.json(); console.log(result.outcome_id, result.matched_by);
Production readiness checklist
The processor-to-Zahlen outcome mapping is documented and contract-tested.
Every executed fixed-schedule attempt can be correlated to request_id and decision_id when available.
Outcome timestamps preserve timezone and represent the actual authorization or settlement event.
Approval and decline evidence is captured without prohibited cardholder data.
HTTP retries reuse stable identifiers and idempotency semantics.
Outcome reporting lag and reporting-completeness percentages are monitored.
Conflicting duplicate outcomes create alerts rather than silent overwrites.
Logs include outcome_id, decision_id, request_id, attempt_number, and matched_by.
Day 1, Day 2, Day 6, and Day 16 are the only payment retry positions used by the integration.
Production smoke tests verify successful, declined, validation-error, 429, timeout, and replay scenarios.
Chapter summary
The Retry Outcome API closes Zahlen’s commercial recovery loop by recording the result that actually occurred after a retry decision or scheduled attempt. Clients submit POST /v1/retry-outcome with attempt_number and outcome, enrich the report with decision, token, processor, amount, and timestamp evidence, and persist the returned outcome_id and matched_by fields. Reliable outcome reporting provides the observed Recovery Truth needed to measure performance across the fixed Day 1, Day 2, Day 6, and Day 16 schedule.
Next chapter |
Chapter 9 explains how investigation runs connect ingested evidence and outcome history to larger operational analysis and reporting. |