ZAHLEN

Appendix B - JSON Examples

Zahlen API User Guide


For merchants, developers, and integration engineers


Version 1.0 | Source baseline: zahlen_deploy_0616A.tar.gz | June 2026

Appendix B - JSON Examples


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.

    1. Conventions

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

    2. Payment Event Submission

      1. Single-event request

        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"

        }

        }

        ]

        }

      1. Example ingestion response


        {

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

    1. Batch Payment Event Submission

      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

}

}

      1. Batch-detail response

        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

        }

        }

    1. Batch Summary and Decisions

      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

image

{

"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

}

}

    1. Event, Decision, and Processor Result Retrieval

      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"

}

}

    1. Legacy Retry Decision

      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.

    1. Legacy Batch Retry Decision

      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

}

    1. Next-Generation Retry Decision

      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.

    1. Retry Outcome Reporting

      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.

    1. Webhook Subscriptions

      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.

    1. Health, Version, and Errors

      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"

}

      1. Validation error

        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

        }

        }

      1. Authentication error

        Illustrative HTTP 401 body


        {

        "error": {

        "code": "INVALID_API_KEY",

        "message": "Authentication failed"

        },

        "meta": {

        "request_id": "req_error_002", "status_code": 401

        }

        }

      1. Rate-limit response

        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.

    1. Copy-and-Test Checklist


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.