ZAHLEN
Payment Events API
![]()
Submit events, batch ingestion, event retrieval, and decision retrieval
Audience
Merchants | Developers | Integration Engineers
Commercial workflow |
Payment Event -> Retry Decision -> Retry Outcome -> Investigation Run -> Reporting |
Zahlen API User Guide v1.0
Source baseline: zahlen_deploy_0616A.tar.gz | June 2026
Learning objectives |
By the end of this chapter, you should be able to submit one or many payment events, validate ingestion results, retrieve individual events, and consume generated retry decisions safely. |
Payment events are the evidence layer of Zahlen. A merchant sends observed authorization or decline information; Zahlen validates and normalizes that evidence, creates durable batch and job identifiers, and makes related event, decision, summary, and processor-result resources available for retrieval.
Canonical retry schedule |
Zahlen uses the fixed retry schedule Day 1, Day 2, Day 6, and Day 16. Payment-event evidence should accurately identify the attempt number and timing so the event can be interpreted within this schedule. |
Method | Path | Purpose |
POST | /v1/payment-events | Submit one or more events using the standard ingestion response. |
POST | /v1/payment-events/batch | Submit one or more events using the batch- oriented response. |
GET | /v1/payment-events/{event_id} | Retrieve normalized and raw event detail. |
GET | /v1/payment-events/{event_id}/decision | Retrieve the decision generated for one event. |
GET | /v1/payment-events/{event_id}/processor- result | Retrieve downstream processor execution evidence. |
GET | /v1/payment-events/batches/{batch_id} | Retrieve batch state and paginated event IDs. |
GET | /v1/payment-events/batches/{batch_id}/ summary | Retrieve distributions and retry-candidate counts. |
GET | /v1/payment-events/batches/{batch_id}/ decisions | Retrieve paginated decisions for a batch. |
GET | /v1/payment-events/batches/{batch_id}/ processor-results | Retrieve paginated processor results. |
All merchant-facing requests use the X-API-Key header. Tenant ownership is derived from the authenticated key; clients do not choose tenant ownership by including tenant_id in the JSON body.
Each item in the events array is a PaymentEventInput. Only event_id is required by the schema, but richer evidence improves explanation, correlation, reporting, and operational diagnosis. Unknown top-level fields are rejected because additional properties are forbidden.
Field | Type | Rule | Purpose |
event_id | string | Required; minimum length 1 | Stable merchant-generated identifier for one payment event. |
Group | Fields | Integration guidance |
Decline evidence | decline_code, response_code, paymentech_code, decline_category | Send the processor evidence available at the time of the event. |
Issuer context | issuer_bin or bin, issuer, bank, country, card_brand | Use safe issuer-level attributes; never send a full PAN. |
Money | amount or amount_minor, currency | Choose units consistently. amount_minor is an integer minor-unit value. |
Retry sequence | attempt_number, attempt_day_in_cycle | Align attempts with the fixed Day 1, 2, 6, and 16 schedule. |
Timing | decline_timestamp, event_timestamp | Use ISO 8601 strings and preserve the actual observed time. |
Merchant correlation | payment_token, customer_id, subscription_id | Use tokenized or merchant-safe references. |
Processor context | processor, authorization_id, authorization_latency_ms | Useful for execution analysis and diagnostics. |
Transaction context | merchant_category_code, recurring_indicator, transaction_initiator | Clarifies recurring and merchant-initiated payment behavior. |
Extensions | metadata | Use only for safe, non-sensitive merchant context. |
Sensitive-data rule |
Do not place a full card number, CVV, password, bank credential, or other prohibited secret in any field, including metadata. Use tokens and the minimum evidence necessary. |
POST /v1/payment-events accepts PaymentEventsIngestRequest. Although this section demonstrates one event, the request body always contains an events array. The array must contain at least 1 and no more than 10,000 events. source is optional and defaults to payment_events_api.
curl -sS -X POST "$ZAHLEN_BASE_URL/v1/payment-events" \
-H "Content-Type: application/json" \
-H "X-API-Key: $ZAHLEN_API_KEY" \
-d '{
"events": [
{"event_id": "evt_20260616_0001"}
]
}'
{
"source": "subscription_billing", "events": [{
"event_id": "evt_20260616_0001", "decline_code": "51",
"issuer_bin": "411111", "card_brand": "VISA", "amount_minor": 2999, "currency": "USD", "attempt_number": 1,
"attempt_day_in_cycle": 1, "event_timestamp": "2026-06-16T12:00:00Z", "payment_token": "tok_customer_001", "customer_id": "cus_001", "subscription_id": "sub_001",
"processor": "example_processor", "metadata": {"invoice_id": "inv_1042"}
}]
}
events is required and must contain 1 through 10,000 items.
Each event_id must be a non-empty string.
amount and amount_minor cannot be negative.
attempt_number, when supplied, must be at least 1.
attempt_day_in_cycle and authorization_latency_ms cannot be negative.
Unknown top-level request or event fields produce a validation failure rather than being silently ignored.
The standard ingestion response confirms receipt and provides identifiers required for later retrieval and support correlation. Persist these identifiers before continuing the workflow.
Field | Meaning | Client action |
status | Current ingestion status | Treat as the authoritative state, not as a settlement result. |
merchant_id | Authenticated merchant context | Use for diagnostics; do not use it to infer tenant ownership. |
tenant_id | Resolved tenant context; nullable in schema | Never submit or override this value from client input. |
payment_event_batch_id | Batch created for the request | Store with the local ingest operation. |
upload_job_id | Background processing / investigation correlation | Log and retain for status and support investigations. |
received_event_count | Events received by the route | Compare with the local intended count. |
total_rows / valid_rows / invalid_rows | Validation and row totals | Alert on unexpected invalid or missing rows. |
error_count | Recorded ingestion errors | Inspect ingestion detail when nonzero. |
created_at / completed_at | Lifecycle timestamps | completed_at may be null while work continues. |
ingestion | Additional ingest detail object | Treat as extensible and avoid brittle assumptions. |
Acceptance is not downstream completion |
A successful POST proves that the API accepted the request according to its response. It does not prove that every downstream decision, investigation, or reporting bridge has completed. Preserve upload_job_id and check the appropriate retrieval resources. |
Identifier | Created by | Use |
event_id | Merchant | Stable correlation for one event. |
payment_event_batch_id / batch_id | Zahlen | Groups an ingestion request. |
upload_job_id | Zahlen | Connects ingestion to background processing and investigation. |
decision_id | Zahlen | Identifies one generated retry decision. |
request_id | Zahlen | Trace and support correlation. |
POST /v1/payment-events/batch uses the same PaymentEventsIngestRequest model but returns PaymentEventsBatchIngestResponse. Use it when the integration naturally groups multiple observations and needs an explicit batch resource.
curl -sS -X POST "$ZAHLEN_BASE_URL/v1/payment-events/batch" \
-H "Content-Type: application/json" \
-H "X-API-Key: $ZAHLEN_API_KEY" \
--data-binary @payment-events.json
{
"source": "nightly_billing_export", "events": [
{
"event_id": "evt_batch_0001", "decline_code": "51",
"attempt_number": 1,
"attempt_day_in_cycle": 1
},
{
"event_id": "evt_batch_0002", "decline_code": "91",
"attempt_number": 2,
"attempt_day_in_cycle": 2
}
]
}
Field | Meaning |
submitted | Number presented in the request. |
accepted | Number accepted for processing. |
rejected | Number rejected. |
batch_id | Durable batch identifier. |
events_url | URL for retrieving the batch events resource. |
upload_job_id | Background-processing correlation. |
created_at / completed_at | Lifecycle timestamps. |
ingestion | Optional extensible ingestion details. |
Validate the entire payload locally before transmission.
Use unique event_id values unless replay behavior has been explicitly designed.
Persist a local batch record before sending.
Store batch_id and upload_job_id immediately after success.
Compare submitted, accepted, and rejected counts.
Do not split or repeat batches in a way that creates extra payment attempts outside Day 1, Day 2, Day 6, and Day 16.
Use the batch resource to inspect state and obtain event IDs. The detail, decision, and processor-result list routes use offset-based pagination. offset must be at least 0; limit, when supplied, must be from 1 through 1,000.
curl -sS \
-H "X-API-Key: $ZAHLEN_API_KEY" \
"$ZAHLEN_BASE_URL/v1/payment-events/batches/$BATCH_ID?offset=0&limit=250"
Resource | Important fields |
Batch detail | status, submitted, accepted, rejected, event_ids, total_events, returned, offset, limit, has_more |
Batch summary | event_count, retry_candidates, top processors, decline codes, issuers, issuer BINs, card brands, confidence and decision distributions |
Batch decisions | decisions, total_events, returned, offset, limit, has_more, decisions_source |
Batch processor results | processor_results, total_events, returned, offset, limit, has_more, processor_results_source |
offset = 0
limit = 250 while True:
page = get_batch_decisions(batch_id, offset=offset, limit=limit) process(page.get("decisions", []))
if not page.get("has_more", False): break
offset += page["returned"]
Pagination safety |
Advance by the returned count and stop when has_more is false. Protect against a zero returned count with has_more=true so a malformed or transient response cannot create an infinite loop. |
GET /v1/payment-events/{event_id} returns PaymentEventDetailResponse. The result combines durable correlation fields, commonly used normalized attributes, the normalized_event object, and the raw_event object.
curl -sS \
-H "X-API-Key: $ZAHLEN_API_KEY" \
"$ZAHLEN_BASE_URL/v1/payment-events/evt_20260616_0001"
Response area | Use |
Correlation | event_id, payment_event_batch_id, upload_job_id, source, source_row_number |
Merchant context | merchant_id and resolved tenant_id when returned |
Normalized common fields | decline_code, response_code, issuer_bin, issuer, country, card_brand, amount_minor, currency, attempt_number, event_timestamp, processor, authorization_id |
normalized_event | Preferred machine-readable normalized representation for application logic. |
raw_event | Original or source representation for diagnostics and evidence review. |
created_at / updated_at | Persistence lifecycle timestamps. |
Normalized versus raw |
Use normalized fields for routine client logic. Preserve raw_event for diagnostics, but do not build fragile production logic around processor-specific raw keys unless a separate contract guarantees them. |
A 404 can mean that the event does not exist or is not visible to the authenticated tenant. Verify the environment, API key, and exact event_id. Never bypass tenant filters to make a resource appear.
GET /v1/payment-events/{event_id}/decision returns PaymentEventDecisionResponse. This is the recommendation generated from the event evidence. A recommendation is not the observed processor outcome and must not be treated as recovered revenue.
curl -sS \
-H "X-API-Key: $ZAHLEN_API_KEY" \
"$ZAHLEN_BASE_URL/v1/payment-events/evt_20260616_0001/decision"
Field | Interpretation |
decision | Explicit control signal. Follow this value rather than inferring action from other fields. |
recommended_retry_day | Nullable recommended day. When present, it should align with the fixed Day 1, 2, 6, or 16 schedule. |
confidence / confidence_score | Evidence strength, not a guarantee of payment success. |
reason_codes | Machine-readable explanation categories. |
reason_detail | Human-readable explanation; nullable. |
policy_source / matched_policy_id | Policy provenance when available. |
decision_id / request_id | Durable identifiers for logging, outcome reporting, and support. |
idempotent_replay | Indicates that an earlier logical operation was safely replayed. |
event | Event evidence associated with the decision. |
issuer_context | Optional issuer-level context used or returned by the decision flow. |
Never invent a retry day |
If recommended_retry_day is null, do not select an arbitrary day. Follow the decision and reason fields. Where a retry is authorized by Zahlen, execution must remain within the fixed Day 1, Day 2, Day 6, and Day 16 schedule. |
The decision tells the merchant what Zahlen recommends. GET
/v1/payment-events/{event_id}/processor-result reports downstream execution evidence, including result, processor reference, response code, description, and processing time. Chapter 8 explains how to report retry outcomes and close the learning loop.
Create a stable event_id and persist the local event record.
Validate the payload and submit it with X-API-Key.
Persist payment_event_batch_id or batch_id and upload_job_id from the response.
Retrieve the event and verify normalized evidence when needed.
Retrieve the generated decision and preserve decision_id and request_id.
Execute only the authorized payment action and only on the fixed Day 1, Day 2, Day 6, or Day 16 schedule.
Report the observed retry outcome through the Retry Outcome API.
Monitor investigation and reporting resources for broader patterns.
Status | Meaning in this workflow | Response |
400 | Malformed or business-invalid request | Correct the payload; do not blindly retry. |
401 | Missing, invalid, revoked, or wrong- environment API key | Verify X-API-Key and environment. |
403 | Authenticated but not permitted | Check plan, capability, or endpoint authorization. |
404 | Event or batch absent or not tenant-visible | Verify identifier and authenticated tenant context. |
409 | Conflict or replay mismatch | Compare the logical operation and original request. |
422 | Schema validation failure | Correct field spelling, types, limits, and unknown properties. |
429 | Rate or quota enforcement | Back off and honor Retry-After when present. |
500/503 | Server or dependency failure | Retry safely with backoff; preserve stable identifiers and idempotency behavior. |
API keys are loaded from a secret manager, not source code.
event_id uniqueness and replay behavior are documented.
Client models reject unknown fields before sending.
amount and amount_minor units cannot be confused.
All timestamps are produced and parsed consistently.
Batch counts and invalid-row counts are monitored.
Batch pagination is bounded and loop-safe.
Identifiers are included in logs without exposing secrets.
Decision handling supports nullable fields.
Payment execution is constrained to Day 1, Day 2, Day 6, and Day 16.
Observed outcomes are reported separately from recommendations.
Chapter takeaway |
A reliable Payment Events integration preserves evidence and identifiers. Submit accurate observations, treat ingestion as an asynchronous durable workflow, retrieve normalized events and explicit decisions, and keep recommendation, execution, and outcome as separate facts. |