ZAHLEN

API User Guide

Chapter 10 - Webhooks

Outcome contract | Verification | Retry handling


Audience

Merchants, developers, and integration engineers who build or operate webhook consumers for Zahlen

events.


Version 1.0 | Source baseline: zahlen_deploy_0616A.tar.gz | June 2026 Commercial developer experience | Tenant-safe operations | Explainable retry intelligence

Chapter 10 - Webhooks


Learning objectives

By the end of this chapter, you should be able to create and manage webhook subscriptions, interpret the outcome-delivery contract, verify deliveries using the active deployment policy, and implement retry-safe,

duplicate-safe processing.

Webhooks allow Zahlen to send event notifications to a merchant-controlled HTTPS endpoint. Instead of repeatedly polling for a change, the merchant registers a callback URL and one or more event types. When a subscribed event occurs, Zahlen can deliver a request to that callback according to the active deployment contract.

The confirmed merchant-facing subscription surface contains three operations:


Method

Path

Purpose

POST

/v1/webhook-subscriptions

Create a tenant-scoped subscription.

GET

/v1/webhook-subscriptions

List subscriptions visible to the

authenticated merchant.

DELETE

/v1/webhook-subscriptions/

{subscription_id}

Delete or deactivate a subscription.


Contract boundary

The uploaded schema confirms subscription management fields, but it does not define one universal delivery payload, signature algorithm, signing header, or retry timetable. Production clients must obtain the active

webhook outcome contract and verification policy from their Zahlen deployment.


    1. Why webhooks matter

      In the Zahlen commercial workflow, a decision is not the same as an observed result. Webhooks can notify downstream systems that durable outcome or operational evidence is available, reducing polling and helping merchants keep their own records synchronized.

      • Use webhooks for notifications, not as the only system of record.

      • Persist durable Zahlen identifiers such as decision ID, request ID, outcome ID, event ID, and subscription ID.

      • Design for delayed, duplicated, and out-of-order delivery.

      • Keep payment scheduling separate from HTTP delivery retries. Webhook retries must never create extra payment attempts outside Zahlen's fixed Day 1, Day 2, Day 6, and Day 16 schedule.

    2. Subscription contract

      All merchant-facing subscription calls use the X-API-Key header. Tenant and merchant ownership are derived from the authenticated key rather than trusted from the JSON body.

      Create a subscription


      curl -sS -X POST "$ZAHLEN_BASE_URL/v1/webhook-subscriptions" \

      -H "Content-Type: application/json" \

      -H "X-API-Key: $ZAHLEN_API_KEY" \

      -d '{

      "callback_url": "https://merchant.example.com/webhooks/zahlen", "events": ["REPLACE_WITH_ACTIVE_EVENT_TYPE"]

      }'


Illustrative event value

REPLACE_WITH_ACTIVE_EVENT_TYPE is intentionally not a real contract promise. Obtain the enabled event-

type catalog from the deployed Zahlen outcome contract before creating a production subscription.


Field

Type

Required

Constraints and meaning


callback_url


string


Yes

Minimum length 8; maximum length 2,048. Use an HTTPS endpoint in

production.

events

array[string]

Yes

At least 1 and at most 20

event-type strings.


Unknown top-level properties are rejected because the create model forbids extra fields. This protects integrations from silently sending misspelled or unsupported configuration.

Create response


{

"subscription_id": "whsub_01J...", "merchant_id": "merchant_example", "tenant_id": "tenant_example",

"callback_url": "https://merchant.example.com/webhooks/zahlen", "events": ["REPLACE_WITH_ACTIVE_EVENT_TYPE"],

"status": "ACTIVE",

"created_at": "2026-06-16T14:00:00Z", "updated_at": "2026-06-16T14:00:00Z",

"deleted_at": null

}


Example values

Identifier formats and status values above are illustrative. Parse the documented fields, but do not hard-code

invented prefixes or status catalogs unless your deployment contract defines them.

    1. Response fields and lifecycle


      Field

      Type

      Required

      Client use


      subscription_id


      string


      Yes

      Stable identifier for later deletion, logs, support, and

      audit.

      merchant_id

      string

      Yes

      Merchant context resolved

      by Zahlen.

      tenant_id

      string

      Yes

      Tenant ownership resolved

      from authentication.

      callback_url

      string

      Yes

      Registered destination.

      events

      array[string]

      Yes

      Subscribed event types.

      status

      string

      Yes

      Current subscription state.

      created_at

      string

      Yes

      Creation timestamp.

      updated_at

      string

      Yes

      Most recent update

      timestamp.

      deleted_at

      string or null

      No

      Deletion/deactivation

      timestamp when applicable.


      List subscriptions


      curl -sS "$ZAHLEN_BASE_URL/v1/webhook-subscriptions" \

      -H "X-API-Key: $ZAHLEN_API_KEY"

The list response contains merchant_id, tenant_id, count, and subscriptions. Treat an empty list as a valid tenant-scoped result; do not bypass tenant filters to find subscriptions belonging to another account.

Delete or deactivate a subscription


curl -sS -X DELETE \

"$ZAHLEN_BASE_URL/v1/webhook-subscriptions/whsub_01J..." \

-H "X-API-Key: $ZAHLEN_API_KEY"


Delete response field

Type

Meaning

subscription_id

string

Subscription acted upon.

merchant_id

string

Authenticated merchant context.

tenant_id

string

Authenticated tenant context.

deleted

boolean

Whether deletion/deactivation

occurred.

status

string

Resulting subscription status.

deleted_at

string or null

Deletion time when supplied.


Operational rule

Store subscription_id locally. Do not discover it by matching callback URLs during an outage or deployment

change.

    1. Outcome contract

      The outcome contract describes what Zahlen sends, when it sends it, how the receiver identifies the event, and how authenticity is verified. It is distinct from the subscription-management schema.


      Contract area

      Questions the production contract must answer

      Event catalog

      Which event-type strings may be subscribed to? Which

      versions are active?

      HTTP request

      Which method, content type, timeout, and headers are

      used?

      Envelope

      Where are event ID, delivery ID, event type, version, and

      creation time located?

      Payload

      Which outcome, decision, payment-event, and merchant

      correlation fields are included?

      Verification

      Which signature algorithm, secret, header names,

      timestamp rules, and canonical byte sequence apply?

      Acknowledgment

      Which HTTP response codes count as successful delivery?

      Retry policy

      Which failures are retried, how often, and for how long?

      Replay

      How can an authorized operator replay a failed delivery?


      Recommended consumer envelope

      A robust client should be able to process an envelope with stable delivery metadata and a versioned payload. The following structure is conceptual only and must not replace the active deployment contract:

      {

      "event_id": "provider-defined stable event identifier", "delivery_id": "provider-defined delivery attempt identifier", "event_type": "contract-defined event type", "schema_version": "contract-defined version",

      "created_at": "ISO-8601 timestamp", "data": { "contract-defined payload": true }

      }


Do not guess

Do not infer field names, event names, or signature headers from this conceptual envelope. Generate

production parsing and verification from the actual Zahlen outcome contract.


Correlation with retry outcomes

Where the active event carries retry-outcome evidence, correlate it using durable identifiers instead of customer-readable labels. The retry outcome API can expose outcome_id, request_id, decision_id, token, attempt_number, outcome, processor evidence, timestamp, and matched_by. A webhook consumer should store whichever of these fields are included by the active contract and avoid declaring recovery based only on a scheduled attempt.

    1. Verification

      Verification proves that an inbound request was created by the expected Zahlen deployment and was not modified in transit. Because the 0616A subscription schema does not define a universal signing mechanism, the steps below describe the required security pattern without inventing algorithm-specific details.

      1. Read the exact raw request body before JSON parsing or reformatting.

      2. Read the contract-defined signature, timestamp, key identifier, and version headers.

      3. Reject missing or unsupported verification metadata.

      4. Check that the delivery timestamp falls within the allowed replay window.

      5. Compute the expected signature using the contract-defined algorithm and canonical input.

      6. Compare signatures using a constant-time comparison function.

      7. Deduplicate the stable event or delivery identifier before applying business effects.

      8. Only then parse and process the payload.


        Raw bytes matter

        Many signing schemes authenticate the exact HTTP body bytes. Parsing JSON and serializing it again can

        change whitespace or property ordering and cause valid signatures to fail.


        Illustrative verification pseudocode


        raw_body = request.read_raw_bytes()

        metadata = read_contract_headers(request.headers)


        if metadata.missing_or_unsupported():

        return HTTP_401


        if timestamp_outside_allowed_window(metadata.timestamp): return HTTP_401


        expected = contract_sign(secret, metadata, raw_body)

        if not constant_time_equal(expected, metadata.signature): return HTTP_401


        envelope = parse_json(raw_body)

        if already_processed(envelope.stable_event_id): return HTTP_200

        enqueue_for_processing(envelope) return HTTP_200

Secret management

    1. Reliable consumer architecture

      The callback handler should do as little synchronous work as possible. Long-running business logic increases timeout risk and can cause Zahlen to retry a delivery that actually reached your system.


      Stage

      Responsibility

      Failure behavior

      1. Receive

      Accept HTTPS request and retain raw

      bytes.

      Reject malformed transport safely.

      2. Verify

      Authenticate signature and replay

      timestamp.

      Return the contract-defined

      authentication failure.

      3. Deduplicate

      Reserve stable event ID in durable

      storage.

      Previously completed event returns

      success without reapplying effects.

      4. Persist

      Store envelope, headers needed for

      audit, and receive time.

      Do not acknowledge if durable

      acceptance failed.

      5. Enqueue

      Place work on an internal queue.

      Use transactional outbox/inbox

      patterns where practical.

      6. Acknowledge

      Return success quickly.

      Use only success codes recognized by

      the active contract.

      7. Process

      Apply idempotent business logic

      asynchronously.

      Retry internally without requiring

      another external delivery.


      Separate receipt from business completion

      A successful webhook response should ordinarily mean the event was verified and durably accepted, not that

      every downstream workflow has finished.


      Deduplication record


      Stored value

      Purpose

      stable_event_id

      Prevents duplicate business effects across repeated

      deliveries.

      delivery_id

      Supports per-attempt diagnostics when the contract

      supplies it.

      event_type and schema_version

      Selects the correct parser and handler.

      received_at and processed_at

      Measures delivery and processing lag.

      payload hash

      Supports integrity and duplicate diagnostics without storing

      excess sensitive data.

      processing status and error

      Supports internal retry and operations.

    2. Retry handling

      Webhook retry handling has two sides: Zahlen may retry delivery when the callback is unavailable, and the merchant may retry internal processing after the callback has been durably accepted. Both sides must be duplicate-safe.

      Provider delivery retries

      • Expect the same logical event more than once.

      • Do not use arrival count as a business quantity.

      • Return the contract-defined success response for an already-processed event.

      • Do not deliberately fail duplicates to force another delivery.

      • Use administrative operations for authorized replay rather than editing delivery records directly.

        Merchant internal retries

      • Retry downstream database, queue, notification, or reporting work using the stored event ID.

      • Make every side effect idempotent: use unique keys, compare-and-set transitions, or transactional records.

      • Apply bounded exponential backoff with jitter to transient internal failures.

      • Move repeatedly failing work to a dead-letter or manual-review queue without losing the original envelope.

        delay = min(max_delay, base_delay * (2 ** retry_number))

        delay = delay * random.uniform(0.75, 1.25)

HTTP response guidance


Condition

Typical receiver behavior

Why

Valid and durably accepted

Return an allowed success code

quickly.

Prevents unnecessary provider retries.

Valid duplicate already processed

Return an allowed success code.

Duplicate delivery is normal network

behavior.

Invalid signature or stale replay

Return the contract-defined

authentication failure.

Do not process unauthenticated

content.

Malformed payload

Return the contract-defined client

failure and quarantine evidence.

Blind retries usually cannot repair

invalid content.

Temporary inability to persist

Return a retryable failure only if the

contract defines it.

Provider retry may recover a transient

outage.

Downstream business system

unavailable after durable acceptance

Acknowledge and retry internally.

Avoid coupling callback latency to

downstream health.


Payment retry boundary

A duplicate or delayed webhook must not trigger an additional card authorization. Payment attempts remain

governed by the fixed Day 1, Day 2, Day 6, and Day 16 schedule and the explicit Zahlen decision/outcome workflow.

    1. Ordering, versions, and schema evolution

      Network delivery does not guarantee that related events arrive in the order they were created. Consumers should use timestamps, versions, and durable state transitions instead of assuming arrival order is business order.

      • Ignore or quarantine unsupported event types rather than treating them as a known event.

      • Route each schema_version to a compatible parser when version metadata exists.

      • Allow additive optional fields without breaking parsing, while still validating required fields.

      • Do not overwrite newer local state with an older event solely because it arrived later.

      • Keep contract tests for current and prior supported payload versions.

    2. Test plan


      Test

      Expected result

      Valid signed delivery

      Verified, stored, queued, and acknowledged once.

      Duplicate valid delivery

      No duplicate business effect; successful acknowledgment.

      Invalid signature

      Rejected before payload processing.

      Missing verification metadata

      Rejected according to the active contract.

      Old replay timestamp

      Rejected outside allowed replay window.

      Unknown event type

      Safely ignored or quarantined with an operational signal.

      Out-of-order related events

      State remains correct and does not regress.

      Temporary database outage before persistence

      Retryable response according to contract.

      Downstream outage after persistence

      Callback succeeds; internal work retries.

      Consumer timeout

      No partial untracked business effect.

      Secret rotation overlap

      Deliveries signed with permitted current/previous keys

      verify correctly.

      Deleted subscription

      No new deliveries expected after contract-defined

      deactivation behavior.

    3. Implementation examples

      image

      from flask import Flask, request, jsonify app = Flask( name )

      @app.post("/webhooks/zahlen") def zahlen_webhook():

      raw_body = request.get_data(cache=True)

      metadata = read_contract_headers(request.headers)


      if not verify_with_active_contract(raw_body, metadata): return jsonify({"accepted": False}), 401


      envelope = parse_and_validate_contract_payload(raw_body) event_id = stable_event_id(envelope)


      if inbox_already_completed(event_id):

      return jsonify({"accepted": True, "duplicate": True}), 200


      persist_and_enqueue_atomically(event_id, envelope, metadata) return jsonify({"accepted": True}), 200

      Python callback skeleton


      JavaScript callback skeleton


      app.post("/webhooks/zahlen", rawBodyMiddleware, async (req, res) => { const rawBody = req.body;

      const metadata = readContractHeaders(req.headers);


      if (!verifyWithActiveContract(rawBody, metadata)) { return res.status(401).json({ accepted: false });

      }


      const envelope = parseAndValidateContractPayload(rawBody); const eventId = stableEventId(envelope);


      if (await inboxAlreadyCompleted(eventId)) {

      return res.status(200).json({ accepted: true, duplicate: true });

      }


      await persistAndEnqueueAtomically(eventId, envelope, metadata); return res.status(200).json({ accepted: true });

      });


Framework warning

Some web frameworks parse JSON before your handler runs. Configure raw-body capture for the webhook

route if the active verification algorithm signs the exact request bytes.

    1. Operations and troubleshooting


      Symptom

      Likely checks


      No deliveries

      Subscription status, enabled event types, callback URL, tenant/key context, and whether the triggering event

      occurred.

      Repeated deliveries

      Receiver timeout, non-success response, persistence failure,

      or expected at-least-once behavior.


      Every signature fails

      Wrong environment secret, body mutation, incorrect canonical input, clock skew, or unsupported contract

      version.

      Some signatures fail

      Secret rotation, multiple key IDs, proxy body

      transformation, or timestamp parsing.

      High processing lag

      Synchronous callback work, queue backlog, downstream

      dependency failure, or insufficient workers.

      Duplicate business actions

      Missing durable inbox key or non-idempotent downstream

      operation.

      Events appear out of order

      Normal network behavior; use event time/version and

      monotonic state transitions.

      Deleted endpoint still receives requests

      In-flight delivery, deactivation semantics,

      cache/propagation delay, or another active subscription.


      Production readiness checklist

      • Callback URL uses HTTPS and has valid TLS.

      • Production event types come from the active Zahlen outcome contract.

      • Raw request body is retained long enough for verification.

      • Verification rejects missing, invalid, and stale authentication metadata.

      • Secrets are stored securely and rotation is tested.

      • Durable deduplication is performed before business effects.

      • Callback persistence and acknowledgment complete quickly.

      • Downstream work is asynchronous and independently retryable.

      • Unknown event types and schema versions are quarantined safely.

      • Duplicate, delayed, out-of-order, and replayed deliveries are tested.

      • Alerts cover signature failures, delivery failure rate, processing lag, and dead-letter growth.

      • Webhook handling cannot create payment attempts outside Day 1, Day 2, Day 6, and Day 16.


Chapter summary

Create, list, and delete subscriptions with X-API-Key. Treat the deployed outcome contract as authoritative for event names, payloads, verification, acknowledgments, and retry policy. Verify raw deliveries, persist before acknowledging, deduplicate with durable identifiers, process asynchronously, and keep webhook retries

completely separate from payment retries.