ZAHLEN

API User Guide

Chapter 16 - SDK Examples

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

Chapter 16 - SDK Examples


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.


    1. Common environment variables


      export ZAHLEN_BASE_URL="https://api.example.com"

      export ZAHLEN_API_KEY="zk_live_REPLACE_ME"

    1. 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

    1. 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.

image

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()

    1. 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.

    2. 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.

    3. 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.

    1. 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.

    1. 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);

    2. 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.

    3. 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.