Skip to main content

Overview

A Feddi event reports a domain change that already committed: a wallet balance moved, a top-up settled, a promotional grant accrued. Each event carries a stable event_id you dedup on and a signature you verify before you trust the body. Webhooks are server-to-server deliveries: Feddi makes an HTTP POST to your endpoint with the event envelope in the body and the signature in a header. The envelope and signature contract is stable to build against now, even before outbound delivery is mounted. This page is the contract you build your receiver against: the event catalog, the event envelope, and the signature-verification steps. All are stable. Build your verifier now, validate it against your own test vectors, and you are ready to receive deliveries the moment they are available to you.
Outbound delivery endpoints are not yet mounted. Until they ship, reconcile settled activity through the transactions read API at Transactions. The envelope and signature contract on this page is what you verify when delivery becomes available, so you can write the receiver against it today.

The event envelope

Every event carries the same envelope. Process each event idempotently on its event_id: the same event can arrive more than once, so dedup before you act.
required
Globally unique. This is your idempotency key for processing. Store it, and treat a second arrival of the same event_id as a no-op.
required
The domain change, for example wallet.balance_changed, topup.confirmed, or promo_grant.accrued. Branch your handler on this value.
required
RFC3339 timestamp of when the underlying domain change happened.
required
RFC3339 timestamp of when Feddi emitted the event, always after the database transaction committed. An event never precedes the change it describes.
required
The Feddi merchant tenant this event belongs to.
required
The event-type-specific payload. Read the fields your handler needs by event_type.
Money inside data always follows the same shape: a minor-units integer paired with an explicit ISO-4217 currency, for example credited_actual_minor: 5000 with currency: QAR.

Event catalog

These are the event types Feddi emits, grouped by domain, with when each one fires.

Identity & enrollment

Money & wallet

Promotion & incentive

Lifecycle & reconciliation

Every payload follows the same envelope; branch your handler on event_type and read the fields your handler needs.

Delivery model

How deliveries behave on the wire, so your receiver tolerates the realities of an at-least-once channel.
  • At-least-once delivery. The same event can arrive more than once. Dedup on event_id.
  • Per-resource ordering. A monotonic sequence_number on the envelope orders events within a single resource. Do not assume global order across resources.
  • Retry with backoff. Any non-2xx response or timeout is retried with exponential backoff, then dead-lettered after retries are exhausted.
  • Replay window. The signed timestamp enforces a replay window. Reject anything outside it before you read the body.
  • Idempotent processing. Key your processing on event_id so a redelivery is a no-op.
  • Endpoint auto-disable. Sustained delivery failure auto-disables the endpoint. Re-enable it from the dashboard.
The delivery sequence:

Subscriptions

This is a forward contract, not yet mounted. When delivery ships, you will:
  • Register a delivery endpoint URL per integration.
  • Receive a per-subscription signing secret.
  • List and disable subscriptions.
  • Rotate the signing secret.
Until then, reconcile through the transactions read API.

Verify every delivery

Feddi signs each delivery with HMAC-SHA256 over a canonical base string, keyed by your subscription secret. Verify the signature and reject anything outside the replay window before you read the body. An unsigned or stale request is not a Feddi event. The signature travels in the Feddi-Signature header as t=<unix-timestamp>,v1=<hex-hmac>. The signing base string is the signed timestamp, a literal ., then the exact raw request body:
1

Read the header

Parse Feddi-Signature. Take t (the signed Unix timestamp) and v1 (the expected hex signature).
2

Reject stale timestamps

If t is outside your replay window (compare against your own clock), reject the request. This is what stops a captured payload from being replayed against you later. We recommend a tolerance of 300 seconds or less.
3

Recompute and compare

Build t + "." + raw_body, compute HMAC-SHA256 with your stored secret, and compare to v1 using a constant-time equality check. Sign the raw bytes you received, never a re-serialized object: any whitespace or key-order change breaks the match.
4

Process idempotently, then 2xx

On a match inside the window, dedup on event_id, act, and return a 2xx. A non-2xx response (or a timeout) signals failure to Feddi.
Compare with a constant-time function (hmac.compare_digest in Python, crypto.timingSafeEqual in Node). A plain string equality check leaks timing information that can be used to forge a signature.

Reconcile through the transactions read API

Until delivery endpoints are available, treat the transactions read API as your source of truth for what settled. Poll it on a schedule, or pull it on demand to confirm an order reconciled.
  • List with GET /{integrationId}/transactions, cursor-paginated and filterable by status, a date window, and branch_id.
  • Read one with GET /{integrationId}/transactions/{transactionId}.
Both reads are tenant-scoped to the integration in the path and your x-api-key. A mismatched {integrationId} resolves to 401 or 403, never another tenant’s rows. See Transactions for the full projection fields each read returns.

Where to go next

Transactions

The settlement reads you reconcile against today, with their full projection fields.

Idempotency & errors

Dedup on event_id the same way you key mutating calls, and handle every typed error code.

Conventions

The response envelope, money-as-minor-units, and the meta block on every response.

Authentication

The x-api-key your reconciliation reads authenticate with.