Skip to main content
The Zeam API is built around an event-driven design. Not every operation completes in a single request-response cycle. Transactions move through multiple internal and external states before reaching a final outcome, and your integration needs to handle that progression. This page explains the design pattern, why webhooks are necessary, and how to build a reliable integration around eventual consistency.

Why operations are asynchronous

When you submit a payment through the Zeam API, the request passes through multiple systems: internal compliance checks, Stellar network settlement, external payment provider submission, and provider-side processing (bank transfers, mobile money delivery, KYC verification). Each of these steps operates on its own timeline. Some examples of operations that are inherently asynchronous:
  • A cross-border bank transfer that takes hours to settle at the destination
  • A mobile money delivery waiting for the recipient’s provider to confirm
  • A KYC check that requires manual review before the transaction can proceed
  • A payment reversal triggered by a provider callback received after the initial confirmation
  • A retry attempt after a temporary provider outage
Because of this, the API acknowledges your request immediately and returns a tracking reference. The actual outcome arrives later, as the workflow progresses through its states.
Treat the initial API response as “accepted for processing”, not “completed”. The final state arrives via webhook or by polling the status endpoint.

Transaction lifecycle

Every transaction moves through a defined set of states. Only two states are terminal — the transaction either completes or fails permanently.
StateTerminalDescription
createdNoRequest accepted, prechecks running
pendingNoPrechecks passed, preparing for submission
processingNoSubmitted to the payment provider, awaiting confirmation
requires_actionNoPaused — requires manual resolution (compliance hold, provider query)
completedYesFunds delivered, ledger finalized
failedYesPermanently rejected — reason code attached
reversedYesPreviously completed, then reversed by the provider or by a compliance action
Do not treat non-terminal states as final. A transaction in processing may still move to completed, failed, or reversed based on provider callbacks.

Why webhooks are required

Polling the status endpoint works for debugging, but it is not sufficient for a production integration:
  • Latency — polling introduces delay between the state change and your system learning about it. Webhooks deliver within seconds.
  • Efficiency — polling at high frequency wastes bandwidth and counts against rate limits. Webhooks deliver only when something changes.
  • Completeness — some state transitions (reversals, compliance holds) happen hours or days after the initial request. Polling would need to run indefinitely.
Webhooks are the primary mechanism for receiving transaction state changes. Use the status endpoint as a fallback to fetch the latest state on demand, not as the primary integration path.

Webhook events

The Zeam platform publishes webhook events when a transaction changes state. Each event represents a single state transition.

Event types

Event typeTrigger
transaction.createdA new transaction has been accepted for processing
transaction.pendingPrechecks passed, preparing for provider submission
transaction.processingSubmitted to the external payment provider
transaction.completedProvider confirmed delivery — funds arrived
transaction.failedPermanently failed — see reason_code for details
transaction.reversedA previously completed transaction was reversed
transaction.requires_actionPaused — requires manual resolution before processing can continue

Example webhook payload

{
  "event_id": "evt_01J7XQKM3P9VWSNC4AHDG8R6YT",
  "event_type": "transaction.completed",
  "created_at": "2026-05-09T10:42:18Z",
  "resource_type": "transaction",
  "resource_id": "txn_01J7XQ8F2KNWM5VR3BPCE6HDJX",
  "association_id": "assoc_01HWX9YZ7T5K8QF3NSDE9B2M0P",
  "application_id": "app_01HWX9ABCDE12345FGHIJK6789",
  "data": {
    "state": "completed",
    "previous_state": "processing",
    "amount": "100.00",
    "currency": "ZAR",
    "beneficiary_id": "ben_01J7XQ1A2B3C4D5E6F7G8H9J0K",
    "connector": "acme-bank-za",
    "provider_reference": "ACME-2026-0509-78432",
    "completed_at": "2026-05-09T10:42:17Z"
  }
}

Signature header

Every delivery includes an HMAC-SHA256 signature in the x-zeam-signature header:
x-zeam-signature: sha256=a1b2c3d4e5f6...
x-zeam-event-id: evt_01J7XQKM3P9VWSNC4AHDG8R6YT
x-zeam-event-type: transaction.completed
Compute HMAC-SHA256 over the raw request body using your webhook secret and compare using constant-time comparison. See Webhooks for verification details and SDK helpers.

Acknowledging delivery

Respond with any 2xx status code and an empty body. Zeam treats the delivery as successful once it receives the response:
HTTP/1.1 200 OK
Content-Length: 0
If your endpoint returns a non-2xx status or does not respond within 10 seconds, Zeam retries the delivery with exponential backoff.

How it fits together

This sequence shows the full lifecycle of an asynchronous payment:
  1. Your application submits the transaction. The API returns a transactionId immediately.
  2. The gateway submits the payment to the external provider. A transaction.processing webhook fires.
  3. The provider sends an asynchronous callback confirming delivery. The gateway updates the state and fires transaction.completed.
  4. Your application receives the webhook. If you need the full transaction record, call the status endpoint.

Client integration guidance

Persist the reference

When the API returns a transactionId, store it immediately. This is the correlation key between your system and the Zeam platform. All subsequent webhook events and status queries use this ID.

Handle eventual consistency

Your integration should be designed around the fact that the final state arrives later:
  • Do not block on the initial response. Accept the tracking ID and return control to your user.
  • React to webhooks. Update your internal records when state-change events arrive.
  • Use the status endpoint as a fallback. If you suspect a missed webhook, poll the status endpoint to reconcile.

Deduplicate events

Webhook deliveries may be retried if your endpoint was temporarily unavailable. Store the event_id from each delivery and skip events you have already processed:
// Pseudocode — check before processing
if eventStore.Exists(event.EventID) {
    w.WriteHeader(http.StatusOK) // acknowledge, don't reprocess
    return
}
eventStore.Save(event.EventID)
processEvent(event)

Verify signatures

Always verify the x-zeam-signature header before trusting the payload. An unverified webhook could be a replay attack or a forged request. See Webhooks for verification code in Go, TypeScript, and Dart.

Fetch full details on demand

Webhook payloads include enough context to identify the transaction and its new state, but they deliberately exclude unnecessary sensitive data. If you need the full transaction record (amounts, beneficiary details, provider metadata), call the status endpoint using the resource_id:
curl -s https://api.zeam.money/gw/v1/connect-status/$RESOURCE_ID \
  -H "Authorization: Bearer $CONNECT_TOKEN" \
  -H "x-zeam-auth: $CONNECT_SECRET" | jq

Best practices

Treat every webhook event as potentially duplicated. Use the event_id as a deduplication key. If your system has already processed an event, acknowledge it with 200 and do nothing.
Verify the HMAC-SHA256 signature on every delivery using constant-time comparison. Never skip verification, even in Sandbox mode. Rotate your webhook secret periodically using the secret rotation endpoint.
Respond to webhook deliveries within 10 seconds. Do not perform heavy processing in the request handler. Accept the event, queue it for async processing, and return 200 immediately.
If your endpoint is unavailable, Zeam retries with exponential backoff. Your processing logic must be safe to run multiple times for the same event. Use database constraints or a deduplication store to enforce this.
Periodically reconcile your records against the Zeam API. Fetch transactions by status to catch any events your webhook endpoint may have missed during an outage. The status endpoint is the source of truth.
Design your user-facing flows around the fact that payment outcomes are not instantaneous. Show pending states in your UI. Do not promise delivery until you receive a transaction.completed webhook. Handle transaction.failed and transaction.reversed gracefully.