Python | curl | JavaScript
Audience |
Merchants, developers, and integration engineers implementing Zahlen in server-side applications, automation scripts, and integration services. |
Version 1.0 | Source baseline: zahlen_deploy_0616A.tar.gz | June 2026
Learning objectives |
By the end of this chapter, you should be able to call the core Zahlen endpoints from curl, Python, and JavaScript; centralize authentication; preserve idempotency and correlation identifiers; and implement safe timeout and error behavior. |
The examples in this chapter are intentionally small enough to understand and strong enough to become the foundation of a production client. They use environment variables for secrets, JSON request bodies, explicit timeouts, and stable identifiers.
Canonical payment schedule |
These code examples transport events, decisions, and outcomes. They must never be used to create payment attempts outside Zahlen's fixed Day 1, Day 2, Day 6, and Day 16 schedule. |
Common environment variables
export ZAHLEN_BASE_URL="https://api.example.com"
export ZAHLEN_API_KEY="zk_live_REPLACE_ME"
Use a secret manager or protected runtime environment for ZAHLEN_API_KEY.
Use separate base URLs and keys for development, staging, and production.
Never expose the key in browser code, a mobile binary, source control, screenshots, or support tickets.
curl examples
curl is useful for connectivity tests, smoke tests, and reproducing an integration issue. Use -sS for clean output and --fail-with-body when available so error bodies remain visible.
Health and version
curl -sS "$ZAHLEN_BASE_URL/v1/health" | python -m json.tool
curl -sS "$ZAHLEN_BASE_URL/v1/version" | python -m json.tool
Submit a payment event
curl --fail-with-body -sS -X POST \ "$ZAHLEN_BASE_URL/v1/payment-events" \
-H 'Content-Type: application/json' \
-H "X-API-Key: $ZAHLEN_API_KEY" \
-d '{
"source": "billing_platform", "events": [{
"event_id": "evt_20260616_0001", "decline_code": "51",
"issuer_bin": "411111",
"amount_minor": 2999, "currency": "USD", "attempt_number": 1,
"event_timestamp": "2026-06-16T12:00:00Z"
}]
}' | python -m json.tool
Request a next-generation retry decision
curl --fail-with-body -sS -X POST \ "$ZAHLEN_BASE_URL/v1/_next/retry-decision" \
-H 'Content-Type: application/json' \
-H "X-API-Key: $ZAHLEN_API_KEY" \
-H 'Idempotency-Key: order-8842-attempt-2' \
-d '{
"attempt_number": 2,
"decline_code": "51",
"issuer_bin": "411111",
"amount_minor": 2999, "currency": "USD"
}' | python -m json.tool
More curl workflow examples
Retrieve an event and its decision
EVENT_ID="evt_20260616_0001"
curl --fail-with-body -sS \
-H "X-API-Key: $ZAHLEN_API_KEY" \
"$ZAHLEN_BASE_URL/v1/payment-events/$EVENT_ID" | python -m json.tool
curl --fail-with-body -sS \
-H "X-API-Key: $ZAHLEN_API_KEY" \
"$ZAHLEN_BASE_URL/v1/payment-events/$EVENT_ID/decision" | python -m json.tool
Report a retry outcome
curl --fail-with-body -sS -X POST \ "$ZAHLEN_BASE_URL/v1/retry-outcome" \
-H 'Content-Type: application/json' \
-H "X-API-Key: $ZAHLEN_API_KEY" \
-H 'Idempotency-Key: outcome-order-8842-attempt-2' \
-d '{
"attempt_number": 2, "outcome": "APPROVED", "request_id": "req_example", "decision_id": "dec_example", "token": "tok_customer_001", "approval_code": "A12345", "settled_amount_minor": 2999, "currency": "USD",
"outcome_timestamp": "2026-06-17T12:05:00Z"
}' | python -m json.tool
Shell history warning |
Inline secrets and sensitive payloads may remain in shell history. Prefer environment variables, protected files, or approved secret-injection tooling. |

from future import annotations
import os
from typing import Any import requests
BASE_URL = os.environ["ZAHLEN_BASE_URL"].rstrip("/") API_KEY = os.environ["ZAHLEN_API_KEY"]
class ZahlenClient:
def init (self, base_url: str = BASE_URL, api_key: str = API_KEY) -> None: self.base_url = base_url.rstrip("/")
self.session = requests.Session() self.session.headers.update({
"X-API-Key": api_key,
"Content-Type": "application/json",
})
def request(self, method: str, path: str, *, json: dict[str, Any] | None = None, idempotency_key: str | None = None) -> dict[str, Any]:
headers = {}
if idempotency_key:
headers["Idempotency-Key"] = idempotency_key response = self.session.request(
method, f"{self.base_url}{path}", json=json, headers=headers, timeout=(5, 25),
)
response.raise_for_status() return response.json()
Python client foundation
The connect/read timeout tuple prevents a process from waiting indefinitely. Production clients should also map HTTP errors into application-specific exceptions and capture safe request correlation metadata.
Python core workflow
client = ZahlenClient()
ingest = client.request("POST", "/v1/payment-events", json={ "source": "billing_platform",
"events": [{
"event_id": "evt_20260616_0001", "decline_code": "51",
"issuer_bin": "411111",
"amount_minor": 2999, "currency": "USD", "attempt_number": 1,
}],
})
print("upload_job_id:", ingest["upload_job_id"])
decision = client.request( "POST",
"/v1/_next/retry-decision", json={
"attempt_number": 2,
"decline_code": "51",
"issuer_bin": "411111",
"amount_minor": 2999, "currency": "USD",
},
idempotency_key="order-8842-attempt-2",
)
print("decision:", decision.get("decision"))
outcome = client.request( "POST",
"/v1/retry-outcome", json={
"attempt_number": 2, "outcome": "APPROVED",
"request_id": decision.get("request_id"), "decision_id": decision.get("decision_id"), "settled_amount_minor": 2999, "currency": "USD",
},
idempotency_key="outcome-order-8842-attempt-2",
)
print("outcome_id:", outcome.get("outcome_id"))
Preserve the identifier chain |
Store event_id, batch_id or payment_event_batch_id, upload_job_id, request_id, decision_id, and outcome_id. These values connect merchant logs, Zahlen audit records, and investigation workflows. |
Python error handling
import time import random import requests
def call_with_backoff(call, max_attempts: int = 5): for attempt in range(max_attempts):
try:
return call()
except requests.HTTPError as exc:
status = exc.response.status_code
if status in {400, 401, 403, 404, 409, 422}:
raise
if status not in {429, 500, 503} or attempt == max_attempts - 1: raise
retry_after = exc.response.headers.get("Retry-After")
delay = float(retry_after) if retry_after else min(30, 2 ** attempt) time.sleep(delay * random.uniform(0.75, 1.25))
except (requests.Timeout, requests.ConnectionError): if attempt == max_attempts - 1:
raise
time.sleep(min(30, 2 ** attempt) * random.uniform(0.75, 1.25))
Condition | Python behavior |
400 / 422 | Raise immediately; correct the payload. |
401 / 403 | Stop and repair credentials or authorization. |
409 | Reconcile the original idempotent operation. |
429 | Honor Retry-After when present and apply jitter. |
500 / 503 | Retry only with a bounded budget and stable idempotency. |
Timeout / connection failure | Treat the result as uncertain and reconcile before repeating a write. |
JavaScript client foundation
const BASE_URL = process.env.ZAHLEN_BASE_URL.replace(/\/$/, ""); const API_KEY = process.env.ZAHLEN_API_KEY;
async function zahlenRequest(path, options = {}) { const controller = new AbortController();
const timer = setTimeout(() => controller.abort(), 25000); try {
const response = await fetch(`${BASE_URL}${path}`, {
...options,
signal: controller.signal, headers: {
"Content-Type": "application/json", "X-API-Key": API_KEY,
...(options.headers || {})
}
});
const body = await response.json().catch(() => ({})); if (!response.ok) {
const error = new Error(`Zahlen HTTP ${response.status}`); error.status = response.status;
error.body = body; throw error;
}
return body;
} finally { clearTimeout(timer);
}
}
Server-side only |
Do not place a merchant API key in browser JavaScript. Use Node.js or another trusted server environment and expose only your own appropriately authorized application endpoints to browsers. |
JavaScript core workflow
const ingest = await zahlenRequest("/v1/payment-events", { method: "POST",
body: JSON.stringify({ source: "billing_platform", events: [{
event_id: "evt_20260616_0001", decline_code: "51",
issuer_bin: "411111",
amount_minor: 2999, currency: "USD", attempt_number: 1
}]
})
});
console.log(ingest.upload_job_id);
const decision = await zahlenRequest("/v1/_next/retry-decision", { method: "POST",
headers: {"Idempotency-Key": "order-8842-attempt-2"}, body: JSON.stringify({
attempt_number: 2,
decline_code: "51",
issuer_bin: "411111",
amount_minor: 2999, currency: "USD"
})
});
console.log(decision);
const outcome = await zahlenRequest("/v1/retry-outcome", { method: "POST",
headers: {"Idempotency-Key": "outcome-order-8842-attempt-2"}, body: JSON.stringify({
attempt_number: 2, outcome: "APPROVED",
request_id: decision.request_id, decision_id: decision.decision_id, settled_amount_minor: 2999, currency: "USD"
})
});
console.log(outcome.outcome_id);
Typed-client checklist
Reject unknown request fields before transmission, matching Zahlen strict models.
Represent nullable response fields explicitly rather than assuming every enrichment exists.
Keep decimal amount fields separate from integer amount_minor and settled_amount_minor fields.
Centralize authentication, timeouts, status mapping, retry budgets, and structured logging.
Use unique event IDs and stable idempotency keys for one logical operation.
Capture server request IDs and durable object identifiers in logs without exposing secrets.
Test 401, 403, 404, 409, 422, 429, 500, 503, timeout, and uncertain-write scenarios.
Production readiness checklist
Check | Ready when |
Secrets | Keys are injected at runtime and never committed or exposed to clients. |
Timeouts | Every network call has connect and read limits. |
Idempotency | Retry decision and outcome writes reuse stable keys. |
Backoff | 429 and transient 5xx responses use bounded exponential backoff with jitter. |
Validation | Client models enforce required fields, bounds, and collection limits. |
Observability | Logs include safe request and resource identifiers. |
Schedule safety | No transport retry can initiate an off-schedule payment attempt. |
Contract tests | Examples are tested against the target environment before release. |
Chapter summary |
curl is ideal for direct inspection, Python provides a concise service-client pattern, and server-side JavaScript fits event-driven integrations. In every language, production quality depends on secure key handling, strict serialization, durable identifiers, explicit timeouts, bounded retries, and respect for Zahlen's fixed Day 1, Day 2, Day 6, and Day 16 payment schedule. |