Zahlen API User Guide
For merchants, developers, and integration engineers
Version 1.0 | Source baseline: zahlen_deploy_0616A.tar.gz | June 2026
Purpose |
This appendix provides copy-ready JSON examples for the confirmed Zahlen commercial API contracts. Replace fictional identifiers, timestamps, and URLs before testing. Field names and limits follow the 0616A schema export. |
Examples are intentionally concise. Optional fields are included only when they help explain correlation, payment evidence, or the fixed retry schedule of Day 1, Day 2, Day 6, and Day 16.
All examples use fictional identifiers and tokenized payment references.
Merchant-facing requests authenticate with the X-API-Key header; the key is not shown inside JSON bodies.
Unknown top-level properties are rejected by strict request models where documented.
Monetary values ending in _minor are integer minor units, such as cents.
Timestamps are ISO 8601 strings in UTC.
Response examples illustrate the documented shape; actual identifiers, timestamps, statuses, and optional fields vary.
Security reminder |
Never place full card numbers, CVV values, passwords, API keys, or raw bank credentials inside metadata or example payloads. |
POST /v1/payment-events
{
"source": "billing_platform", "events": [
{
"event_id": "evt_20260616_0001", "decline_code": "51",
"issuer_bin": "411111", "issuer": "Example Bank", "country": "US", "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", "channel": "subscription_renewal"
}
}
]
}
{
"status": "COMPLETED",
"merchant_id": "merchant_example", "tenant_id": "tenant_example", "payment_event_batch_id": "peb_01JABC123", "upload_job_id": "ZN-2026-06-16-0001", "source": "billing_platform", "received_event_count": 1,
"total_rows": 1,
"valid_rows": 1,
"invalid_rows": 0,
"error_count": 0,
"created_at": "2026-06-16T12:00:01Z",
"completed_at": "2026-06-16T12:00:02Z", "ingestion": {
"mode": "api", "normalized": 1
}
}
Response values are illustrative |
The schema defines the response fields and types, but exact status vocabulary and ingestion metadata are runtime values. Client code should not invent undocumented status transitions. |
Both single and batch ingestion use PaymentEventsIngestRequest. The events array accepts 1 through 10,000 items.
POST /v1/payment-events/batch
{
"source": "nightly_renewal_export", "events": [
{
"event_id": "evt_0001", "decline_code": "51",
"issuer_bin": "411111",
"amount_minor": 2999, "currency": "USD", "attempt_number": 1,
"attempt_day_in_cycle": 1
},
{
"event_id": "evt_0002", "decline_code": "91",
"issuer_bin": "550000",
"amount_minor": 1699, "currency": "USD", "attempt_number": 2,
"attempt_day_in_cycle": 2
},
{
"event_id": "evt_0003", "decline_code": "05",
"issuer_bin": "340000",
"amount_minor": 4999, "currency": "USD", "attempt_number": 3,
"attempt_day_in_cycle": 6
}
]
}
Example batch response
{
"status": "ACCEPTED", "merchant_id": "merchant_example", "tenant_id": "tenant_example", "submitted": 3,
"accepted": 3,
"rejected": 0,
"batch_id": "peb_01JABC456",
"events_url": "/v1/payment-events/batches/peb_01JABC456", "upload_job_id": "ZN-2026-06-16-0002",
"source": "nightly_renewal_export", "created_at": "2026-06-16T12:05:00Z",
"completed_at": null, "ingestion": {
"queued": true
}
}
GET /v1/payment-events/batches/{batch_id}?limit=100&offset=0
{
"batch_id": "peb_01JABC456", "status": "COMPLETED",
"merchant_id": "merchant_example", "tenant_id": "tenant_example", "submitted": 3,
"accepted": 3,
"rejected": 0,
"created_at": "2026-06-16T12:05:00Z",
"completed_at": "2026-06-16T12:05:06Z", "upload_job_id": "ZN-2026-06-16-0002", "source": "nightly_renewal_export", "event_ids": [
"evt_0001", "evt_0002", "evt_0003"
],
"total_events": 3,
"returned": 3,
"offset": 0,
"limit": 100, "has_more": false,
"events_url": "/v1/payment-events/batches/peb_01JABC456", "ingestion": {
"normalized": 3
}
}
GET /v1/payment-events/batches/{batch_id}/summary
{
"batch_id": "peb_01JABC456", "status": "COMPLETED",
"merchant_id": "merchant_example", "tenant_id": "tenant_example", "submitted": 3,
"accepted": 3,
"rejected": 0,
"created_at": "2026-06-16T12:05:00Z",
"completed_at": "2026-06-16T12:05:06Z", "upload_job_id": "ZN-2026-06-16-0002", "source": "nightly_renewal_export", "event_count": 3,
"retry_candidates": 2, "top_processors": [
{
"name": "example_processor", "count": 3
}
],
"top_decline_codes": [
{
"code": "51",
"count": 1
},
{
"code": "91",
"count": 1
},
{
"code": "05",
"count": 1
}
],
"top_issuers": [
{
"issuer": "Example Bank", "count": 2
}
],
"top_issuer_bins": [
{
"issuer_bin": "411111",
"count": 1
}
],
"top_card_brands": [
{
"card_brand": "VISA", "count": 2
}
],
"confidence_distribution": { "HIGH": 1,
"MEDIUM": 1,
"LOW": 1
},
"decision_distribution": { "RETRY": 2,
"DO_NOT_RETRY": 1
},
"events_url": "/v1/payment-events/batches/peb_01JABC456",
"summary_source": "decision_pipeline", "ingestion": {
"normalized": 3
}
}
GET /v1/payment-events/batches/{batch_id}/decisions?limit=100&offset=0

{
"batch_id": "peb_01JABC456", "status": "COMPLETED",
"merchant_id": "merchant_example", "tenant_id": "tenant_example", "submitted": 3,
"accepted": 3,
"rejected": 0,
"created_at": "2026-06-16T12:05:00Z",
"completed_at": "2026-06-16T12:05:06Z", "upload_job_id": "ZN-2026-06-16-0002", "source": "nightly_renewal_export", "event_count": 3,
"total_events": 3,
"returned": 1,
"offset": 0,
"limit": 100, "has_more": false, "decisions": [
{
"event_id": "evt_0001", "merchant_id": "merchant_example", "tenant_id": "tenant_example",
"payment_event_batch_id": "peb_01JABC456", "upload_job_id": "ZN-2026-06-16-0002", "decision_id": "dec_01JABC001", "request_id": "req_01JABC001",
"decision": "RETRY", "recommended_retry_day": 2, "confidence": "HIGH", "confidence_score": 0.92, "reason_codes": [
"INSUFFICIENT_FUNDS_PATTERN"
],
"reason_detail": "Retry on the next approved schedule day.", "policy_source": "zahlen_fixed_schedule", "matched_policy_id": "policy_default",
"idempotent_replay": false, "created_at": "2026-06-16T12:05:04Z",
"source": "payment_events", "event": {
"event_id": "evt_0001"
},
"issuer_context": { "issuer_bin": "411111"
}
}
],
"events_url": "/v1/payment-events/batches/peb_01JABC456", "decisions_source": "decision_pipeline",
"ingestion": { "normalized": 3
}
}
GET /v1/payment-events/{event_id}
{
"event_id": "evt_0001", "merchant_id": "merchant_example", "tenant_id": "tenant_example",
"payment_event_batch_id": "peb_01JABC456", "upload_job_id": "ZN-2026-06-16-0002", "source": "nightly_renewal_export", "source_row_number": 1,
"payment_token": "tok_customer_001", "decline_code": "51", "response_code": "DECLINED", "issuer_bin": "411111",
"issuer": "Example Bank", "country": "US", "card_brand": "VISA", "amount_minor": 2999, "currency": "USD", "attempt_number": 1,
"event_timestamp": "2026-06-16T12:00:00Z", "processor": "example_processor", "authorization_id": "auth_001", "normalized_event": {
"decline_category": "INSUFFICIENT_FUNDS"
},
"raw_event": { "decline_code": "51",
"processor": "example_processor"
},
"created_at": "2026-06-16T12:05:01Z", "updated_at": "2026-06-16T12:05:02Z"
}
GET /v1/payment-events/{event_id}/decision
{
"event_id": "evt_0001", "merchant_id": "merchant_example", "tenant_id": "tenant_example",
"payment_event_batch_id": "peb_01JABC456", "upload_job_id": "ZN-2026-06-16-0002", "decision_id": "dec_01JABC001", "request_id": "req_01JABC001",
"decision": "RETRY", "recommended_retry_day": 2, "confidence": "HIGH", "confidence_score": 0.92, "reason_codes": [
"INSUFFICIENT_FUNDS_PATTERN"
],
"reason_detail": "Retry on Day 2, the next approved fixed-schedule day.", "policy_source": "zahlen_fixed_schedule",
"matched_policy_id": "policy_default", "idempotent_replay": false, "created_at": "2026-06-16T12:05:04Z",
"source": "payment_events", "event": {
"event_id": "evt_0001", "attempt_number": 1
},
"issuer_context": { "issuer_bin": "411111", "issuer": "Example Bank"
}
}
GET /v1/payment-events/{event_id}/processor-result
{
"event_id": "evt_0001", "merchant_id": "merchant_example", "tenant_id": "tenant_example",
"payment_event_batch_id": "peb_01JABC456", "upload_job_id": "ZN-2026-06-16-0002", "processor": "example_processor", "processor_reference": "proc_ref_001", "result": "DECLINED",
"response_code": "51", "response_description": "Insufficient funds", "processed_at": "2026-06-16T12:00:00Z", "source": "processor_callback",
"event": {
"event_id": "evt_0001"
}
}
POST /v1/retry-decision
{
"payment_token": "tok_001", "billing_cycle_id": "cycle_2026_06", "event_ts_iso": "2026-06-16T12:00:00Z", "cycle_start_ts_iso": "2026-06-01T00:00:00Z", "country_code": "US",
"card_network": "VISA", "paymentech_code": "51",
"attempt_day_in_cycle": 1,
"days_until_suspension": 20, "bank": "Example Bank", "issuer_id": "issuer_001", "bin": "411111",
"amount": 29.99,
"currency": "USD", "processor": "paymentech",
"transaction_kind": "RECURRING", "subscription_id": "sub_001", "merchant_reference_id": "inv_1042", "ai_mode": false, "enable_spike_alerts": true, "external_change_mode": "DETERMINISTIC"
}
The legacy response contains nested decision, reasoning, signals, explanations, and meta blocks. Because those nested blocks are deployment-defined structures, clients should parse documented keys while preserving forward-compatible handling for optional content.
Illustrative legacy response shape
{
"decision": { "action": "RETRY",
"recommended_retry_day": 2
},
"reasoning": {
"summary": "Evidence supports the next fixed-schedule attempt."
},
"signals": { "decline_code": "51",
"attempt_day_in_cycle": 1
},
"explanations": [
"The next approved retry day is Day 2."
],
"meta": {
"request_id": "req_legacy_001", "api_version": "v1"
},
"policy": {
"source": "zahlen_fixed_schedule"
}
}
Do not combine contracts |
The legacy RetryDecisionRequest and the next-generation NextRetryDecisionPayload are separate contracts. Do not mix fields from both models in one request. |
BatchRetryDecisionRequest accepts an events array with no more than 500 legacy decision requests.
POST /v1/retry-decision/batch
{
"events": [
{
"payment_token": "tok_001", "billing_cycle_id": "cycle_2026_06_a", "event_ts_iso": "2026-06-16T12:00:00Z", "cycle_start_ts_iso": "2026-06-01T00:00:00Z", "paymentech_code": "51",
"attempt_day_in_cycle": 1
},
{
"payment_token": "tok_002", "billing_cycle_id": "cycle_2026_06_b", "event_ts_iso": "2026-06-16T12:01:00Z", "cycle_start_ts_iso": "2026-06-01T00:00:00Z", "paymentech_code": "91",
"attempt_day_in_cycle": 2
}
]
}
Illustrative batch response shape
{
"results": [
{
"decision": { "action": "RETRY",
"recommended_retry_day": 2
},
"reasoning": {
"summary": "Proceed to Day 2."
},
"signals": {},
"explanations": [], "meta": {
"request_id": "req_001"
}
},
{
"decision": { "action": "RETRY",
"recommended_retry_day": 6
},
"reasoning": {
"summary": "Proceed to Day 6."
},
"signals": {},
"explanations": [], "meta": {
"request_id": "req_002"
}
}
],
"meta": {
"batch_request_id": "batch_req_001"
},
"count": 2
}
POST /v1/_next/retry-decision
{
"merchant_id": "merchant_example", "token": "tok_001", "attempt_number": 2,
"decline_code": "51",
"issuer_bin": "411111", "issuer_name": "Example Bank", "card_brand": "VISA", "issuer_country": "US", "amount_minor": 2999, "currency": "USD",
"decline_category": "INSUFFICIENT_FUNDS", "subscription_id": "sub_001", "invoice_id": "inv_1042",
"order_id": "ord_1042", "processor": "example_processor"
}
The only universally required request field in the next-generation contract is attempt_number, which must be at least 1. Include optional evidence when it is trustworthy and available.
Example response using confirmed top-level fields
{
"request_id": "req_next_001", "decision_id": "dec_next_001", "merchant_id": "merchant_example", "token": "tok_001", "attempt_number": 2,
"decision": "RETRY", "retry_day": 6,
"reason_code": "FIXED_SCHEDULE_NEXT_DAY", "reason_detail": "Attempt 2 advances to Day 6.", "policy_source": "zahlen_fixed_schedule", "matched_policy_id": "policy_default", "confidence": "HIGH",
"created_at": "2026-06-16T12:10:00Z",
"idempotent_replay": false, "issuer_context": {
"issuer_bin": "411111"
},
"explainability_sections": [], "decision_trace": {}
}
Schedule interpretation |
A technical resend of the same API call does not advance the payment schedule. Payment attempts remain limited to Day 1, Day 2, Day 6, and Day 16. |
POST /v1/retry-outcome - recovered example
{
"merchant_id": "merchant_example", "attempt_number": 2,
"outcome": "RECOVERED", "request_id": "req_next_001", "decision_id": "dec_next_001", "token": "tok_001", "approval_code": "APPROVED123", "final_decline_code": null, "settled_amount_minor": 2999, "currency": "USD",
"outcome_timestamp": "2026-06-17T09:15:00Z"
}
POST /v1/retry-outcome - declined example
{
"merchant_id": "merchant_example", "attempt_number": 3,
"outcome": "DECLINED", "request_id": "req_next_002", "decision_id": "dec_next_002", "token": "tok_002", "approval_code": null, "final_decline_code": "51", "settled_amount_minor": null, "currency": "USD",
"outcome_timestamp": "2026-06-21T09:15:00Z"
}
Example outcome response
{
"outcome_id": "out_01JABC001", "merchant_id": "merchant_example", "request_id": "req_next_001", "decision_id": "dec_next_001", "token": "tok_001", "attempt_number": 2,
"outcome": "RECOVERED", "approval_code": "APPROVED123", "final_decline_code": null, "settled_amount_minor": 2999, "currency": "USD",
"outcome_timestamp": "2026-06-17T09:15:00Z", "created_at": "2026-06-17T09:15:01Z",
"matched_by": "decision_id"
}
Report the observed processor or settlement result. A scheduled attempt is not a recovered payment until the authorization or settlement evidence confirms success.
POST /v1/webhook-subscriptions
{
"callback_url": "https://merchant.example.com/webhooks/zahlen", "events": [
"retry.outcome.recorded", "payment_event.decision.ready"
]
}
Illustrative subscription response
{
"subscription_id": "whsub_01JABC001", "merchant_id": "merchant_example", "tenant_id": "tenant_example",
"callback_url": "https://merchant.example.com/webhooks/zahlen", "events": [
"retry.outcome.recorded", "payment_event.decision.ready"
],
"status": "ACTIVE",
"created_at": "2026-06-16T12:20:00Z", "updated_at": "2026-06-16T12:20:00Z"
}
Illustrative list response
{
"merchant_id": "merchant_example", "tenant_id": "tenant_example", "count": 1,
"subscriptions": [
{
"subscription_id": "whsub_01JABC001", "merchant_id": "merchant_example", "tenant_id": "tenant_example",
"callback_url": "https://merchant.example.com/webhooks/zahlen", "events": [
"retry.outcome.recorded"
],
"status": "ACTIVE",
"created_at": "2026-06-16T12:20:00Z", "updated_at": "2026-06-16T12:20:00Z",
"deleted_at": null
}
]
}
Illustrative delete response
{
"subscription_id": "whsub_01JABC001", "merchant_id": "merchant_example", "tenant_id": "tenant_example", "deleted": true,
"status": "DELETED",
"deleted_at": "2026-06-16T12:30:00Z"
}
Event names and verification are deployment-specific |
The confirmed schema defines subscription fields, not a universal event catalog, signing header, or signature algorithm. Use the active Zahlen webhook outcome contract for production. |
GET /v1/health
{
"status": "ok",
"service": "zahlen",
"api_version": "v1",
"time": "2026-06-16T12:30:00Z"
}
GET /v1/version
{
"api_version": "v1", "app_version": "0616A", "rules_version": "rules-2026-06",
"playbook_version": "playbook-2026-06"
}
Illustrative HTTP 422 body
{
"error": {
"code": "VALIDATION_ERROR",
"message": "Request validation failed", "details": [
{
"field": "events.0.event_id", "message": "Field required"
}
]
},
"meta": {
"request_id": "req_error_001", "status_code": 422
}
}
Illustrative HTTP 401 body
{
"error": {
"code": "INVALID_API_KEY",
"message": "Authentication failed"
},
"meta": {
"request_id": "req_error_002", "status_code": 401
}
}
Illustrative HTTP 429 body
{
"error": {
"code": "RATE_LIMITED",
"message": "Request limit exceeded"
},
"meta": {
"request_id": "req_error_003", "status_code": 429,
"retry_after_seconds": 30
}
}
Error-body details can vary by route and middleware. Always branch first on the HTTP status, then parse documented fields defensively.
Step | Check | Why it matters |
1 | Replace the base URL | Use the correct development, staging, or production hostname. |
2 | Set X-API-Key outside JSON | Store the credential in a secret manager or environment variable. |
3 | Replace identifiers | Use unique event IDs, billing-cycle IDs, and idempotency keys. |
4 | Keep strict field names | Unknown top-level properties may produce HTTP 422. |
5 | Validate monetary units | Do not mix decimal amount with integer amount_minor. |
6 | Preserve correlation | Log request_id, decision_id, event_id, batch_id, and upload_job_id. |
7 | Test negative paths | Exercise 401, 403, 404, 409, 422, 429, and transient 5xx handling. |
8 | Protect the schedule | Never let API retries create payment attempts outside Day 1, 2, 6, and 16. |
Publication note |
These examples are teaching aids grounded in the confirmed schema. When an example includes runtime vocabulary or nested flexible objects, treat it as illustrative rather than a universal enumeration. |