Kupinga Kupinga · Databus Enterprise
← API reference User manual FAQ Webhook API →
Enterprise ingestion · registration-gated

The Databus ingestion gateway.

How banks, switches, core-banking systems and message buses feed data into the Kupinga platform. The databus is an operator-provisioned ingestion layer, not a self-serve public API. This page documents the one externally relevant REST surface — the signed webhook receiver — and the provisioned adapter protocols that carry everything else.

Receiver path
/api/v1/databus/webhook/{tenant}/{adapter}
Auth
HMAC-SHA256 (per-adapter secret)
Access
Operator-provisioned
Surface
Enterprise / onboarding

What the databus is

A configuration-driven ingestion layer that collects data from the systems a bank already runs — not an open REST endpoint on the public internet.

This is not the synchronous decision API. If you are scoring a transaction in real time, you want the per-channel /api/v1/evaluate/{channel} routes documented in the REST API reference — API-key authenticated, self-serve, decision returned inline. The databus described here is the bulk / streaming ingestion gateway for everything else: feeds from your switch, core-banking application, message bus, or file drops.

Each integrating institution is a tenant. The platform does not expect your core-banking system to call a single REST endpoint over the open network. Instead, an operator configures one or more adapters against your tenant; each adapter knows how to collect events from a specific source using a specific protocol, normalise them to the platform's canonical schema, and land them on the internal event bus for the detection engine.

Two ingestion planes

The two planes inter-operate over the platform's internal Kafka bus, not over HTTP. Data ingested through the databus feeds the same customer profiles, lists and reference data that the synchronous decision path reads at decision time.

Not the same as /transactions/ingest. The platform also exposes POST /api/v1/transactions/ingest on the synchronous plane — that is the standard, channel-agnostic REST endpoint for submitting a transaction (an API-key alias of /evaluate that scores inline and returns a decision). It is not part of the databus and is not this HMAC-signed, operator-provisioned gateway. Use /transactions/ingest for ordinary REST transaction submission; use the databus receiver below for enterprise, multi-protocol, normalised ingestion of transactional and non-transactional data points.

Provisioning prerequisite

Registration is required before any ingestion. A tenant and at least one adapter must be provisioned by a platform operator before you can push or pull a single event. There is no self-service signup for the databus. The webhook receiver returns 404 for any {tenant}/{adapter} pair that has not been registered.

Provisioning is a control-plane operation performed by an operator holding the databus:admin permission. It is shown here for context only — you do not call these endpoints yourself:

POST/api/v1/databus/ingestion-channelsdatabus:admin
POST/api/v1/databus/adaptersdatabus:admin

The first is an atomic provisioning saga (tenant binding + connector instance + adapter + field mapping); the second registers a single adapter. Both produce the {tenant} and {adapter} slugs you will use on the receiver path, and — for webhook adapters — seed the HMAC signing secret in Vault. You receive these values, plus the secret, out-of-band during onboarding (see registration handshake).

The webhook receiver — the one REST surface

An HMAC-signed inbound endpoint your systems POST events to. This is the only client-facing HTTP ingestion path in the databus.

POST/api/v1/databus/webhook/{tenant}/{adapter}HMAC-SHA256

Once your tenant + a webhook adapter are provisioned, your system pushes events here. The receiver verifies the signature and timestamp, enforces replay protection and per-adapter rate limiting, and forwards the raw payload onto the platform's internal raw event bus (raw.<tenant>.<adapter_kind>.v1). The body is treated as opaque bytes (JSON by default; XML where the adapter is configured for it) and is normalised downstream, not at the HTTP boundary.

  • Method: POST only. Any other method returns 405.
  • Path: the trailing two segments are your provisioned {tenant} and {adapter} slugs.
  • Body: raw bytes, one event per request. Capped at 1 MiB; larger bodies are rejected.
  • Content-Type: application/json by default, or application/xml when the adapter is configured for an XML source.

HMAC-SHA256 signing

Every request is authenticated with a per-(tenant, adapter) HMAC secret, seeded in Vault by the operator during provisioning and shared with you out-of-band. The signature is computed over the timestamp and body together:

Signing string
# The bytes that get signed are the timestamp and the raw body,
# joined by a single literal dot:
signing_string = "<X-Timestamp>" + "." + "<raw request body>"

# X-Signature is the lowercase hex HMAC-SHA256 of that string,
# prefixed with "sha256=":
X-Signature = "sha256=" + hex( HMAC_SHA256(secret, signing_string) )
Sign a request
import hmac, hashlib, time, requests

SECRET = b"<per-adapter-hmac-secret-from-onboarding>"
INGEST = "https://<ingest-host>"          # provided at onboarding — do NOT guess
TENANT = "bank-alpha-ng"
ADAPTER = "core-banking-events"

body = b'{"event":"account.updated","account_number":"0123456789"}'
ts   = str(int(time.time()))                  # unix seconds
sig  = "sha256=" + hmac.new(SECRET, (ts + "." + body.decode()).encode(),
                            hashlib.sha256).hexdigest()

resp = requests.post(
    f"{INGEST}/api/v1/databus/webhook/{TENANT}/{ADAPTER}",
    data=body,
    headers={
        "Content-Type":      "application/json",
        "X-Timestamp":       ts,
        "X-Signature":       sig,
        "X-Idempotency-Key": "evt-2026-06-07-000123",   # optional but recommended
    },
)
print(resp.status_code, resp.json())          # 202 {"event_id": "..."}
Secret rotation has a built-in overlap. During a rotation, the receiver verifies against either the current or the previous secret for a short overlap window — sign with the new secret as soon as you have it, and the old one keeps working until the operator drops it. Use a constant-time comparison on your own side if you ever verify signatures back (hmac.compare_digest in Python).

Request headers

HeaderRequiredMeaning
X-TimestampyesUnix time in seconds. Must be within ±300 seconds (5 minutes) of platform time, otherwise the request is rejected 401 with no detail — there is deliberately no oracle that distinguishes a stale timestamp from a bad signature.
X-Signatureyessha256=<hex> — the lowercase-hex HMAC-SHA256 of "<X-Timestamp>.<body>". The sha256= prefix is required; a header without it fails verification.
X-Idempotency-KeynoOpaque dedup key, honoured for 24 hours. If omitted, the receiver falls back to using the signature as the dedup key — so retries still get replay protection — but an explicit, stable key from your side is strongly recommended.
X-Partition-HintnoOptional ordering key. When set, the raw publisher keys on (tenant, hint) so per-customer / per-account ordering survives onto the raw topic. Absent → tenant-level keying only.
Content-Typerecommendedapplication/json (default) or application/xml per the adapter's configured source format.

Responses & 202 behaviour

The receiver acknowledges acceptance for processing, not a completed decision. A 202 means the event passed authentication and was handed to the raw publisher; normalisation, validation and detection happen asynchronously downstream.

202 Accepted
{
  "event_id": "550e8400-e29b-41d4-a716-446655440000"
}
StatusBodyMeaning
202{"event_id":"…"}Accepted and forwarded to the raw bus. Record event_id for traceability.
200{"status":"duplicate"}Replay of an already-seen idempotency key (or signature). Idempotent no-op — the original already landed.
401Signature failed, timestamp outside the 5-minute window, or the timestamp header was unparseable. No distinguishing detail by design.
404Unknown {tenant}/{adapter} — the pair is not provisioned.
405Method other than POST.
413Body exceeds the 1 MiB limit.
429Per-adapter rate limit exceeded. Carries Retry-After. Default ceiling is 100 requests/second per adapter unless your contract sets otherwise.
500The platform could not durably publish the event. Safe to retry with the same idempotency key.

Idempotency & replay protection

  • Send a stable X-Idempotency-Key derived from your source event ID. Retries with the same key inside the 24-hour window collapse to a single ingested event and return 200 {"status":"duplicate"}.
  • Forgot the key? The receiver uses the signature as the dedup key, so an exact byte-for-byte resend within the window is still deduped — but a re-signed retry (new timestamp ⇒ new signature) would not be. Always prefer an explicit key.
  • Ordering: set X-Partition-Hint to your customer or account identifier when event order matters within that entity.
  • Durability: a 202 means the event was published to the raw bus; if the platform's publish fails you get a 500 and should retry. There is no silent drop.

Worked example — pushing a non-transaction data point

A core-banking adapter pushing a customer profile update (one of the canonical event types). The raw body is whatever your source system emits; the operator-configured field mapping translates it to the canonical shape downstream.

cURL
BODY='{"type":"customer","customer_number":"CUST-0001","kyc_tier":"tier_3","status":"active"}'
TS=$(date +%s)
SIG="sha256=$(printf '%s' "${TS}.${BODY}" | openssl dgst -sha256 -hmac "$SECRET" -r | awk '{print $1}')"

curl -X POST "$INGEST/api/v1/databus/webhook/bank-alpha-ng/core-banking-events" \
  -H "Content-Type: application/json" \
  -H "X-Timestamp: $TS" \
  -H "X-Signature: $SIG" \
  -H "X-Idempotency-Key: cust-0001-kyc-bump-2026-06-07" \
  -H "X-Partition-Hint: CUST-0001" \
  --data "$BODY"
#  => 202  {"event_id":"550e8400-..."}

Adapter protocols — provisioned, not public REST

Everything beyond the webhook receiver is collected through operator-provisioned adapters. These are not public REST endpoints you call; they are configured against your source systems.

Most banks never expose their core-banking system to an outbound HTTPS call per transaction. The databus accommodates that: an operator configures an adapter that matches how your source already speaks — polling it, tailing it, consuming its stream, or receiving its file drops. Each adapter produces the same internal raw events the webhook receiver does, so the downstream pipeline is identical regardless of protocol.

Protocol matrix

ProtocolDirectionTypical sourceHow it's exposed
Inbound webhookSource → platform (push)Event-emitting apps, gatewaysPublic REST receiver (HMAC-signed)
ISO 8583Source → platformPOS / ATM switch trafficProvisioned adapter — not public REST
ISO 20022Source → platformRTGS, SWIFT, NIBSS Instant PaymentsProvisioned adapter — not public REST
KafkaPlatform consumesYour existing event bus / topicsProvisioned consumer adapter
REST pollingPlatform polls sourceCore-banking / vendor REST APIsProvisioned poll adapter (you expose the source API to the platform, not vice-versa)
SQL tail-ingestionPlatform reads sourceCore-banking databaseProvisioned read-only SQL tail adapter
SFTP file dropsSource → platformBatch exports, EOD filesProvisioned SFTP adapter (you drop files; the platform ingests them)
Why this list matters. Only the inbound webhook is a public REST surface, and even that is registration-gated and signed. ISO 8583, ISO 20022, Kafka, REST-poll, SQL-tail and SFTP are configured by an operator against your infrastructure during onboarding — there is nothing for you to call. Choose the protocol that matches how your source system already behaves; the operator builds the adapter and the field mapping to the canonical schema.

Canonical event types

The data points the platform understands. Whatever protocol carries your data, the adapter's field mapping normalises it into one of these canonical types.

Transactions are only one of the data points you can feed the platform. The canonical model defines eleven event types; the per-type field schema is published in the schema registry (Avro) and shared in your onboarding pack.

Canonical typeWhat it represents
transactionA monetary or value-bearing event across any channel (POS, ATM, NIP, RTGS, USSD, mobile, wallet, …). The richest type; mirrors the synchronous /evaluate payload.
accountAn account record or change — open / close, status, Post-No-Debit holds, balances, branch.
customerA customer / CIF profile and its updates — identity, KYC tier, screening status, PEP flag.
deviceDevice telemetry and bindings — device IDs, OS / browser / app fingerprints, first-seen.
sessionA channel session — login, session lifecycle, the activity grouping that stitches multiple actions together.
related_partyCorporate control-structure parties — directors, shareholders, beneficial owners, signatories.
merchantMerchant master data — merchant identity, category (MCC), location.
terminalTerminal / acceptance-device registry — POS terminals, ATM registry, location, deployment type.
name_enquiryAccount name-enquiry results — the NIP / inter-bank lookups that resolve a NUBAN to an account name.
blocklistBlocklist / watchlist / allowlist feed entries — entities you want the list checker to act on.
adverse_mediaAdverse-media and screening intelligence — negative-news and watch signals attached to an entity.
"Other data points" in practice. The webhook receiver and adapters are how you keep these non-transaction types fresh — e.g. push a customer KYC change, a device binding, a blocklist feed, or account status updates. They feed the same customer profiles, lists and velocity dimensions the detection engine reads when it scores the next transaction.

The normalisation pipeline

What happens to an event between acceptance and the detection engine.

Ingestion accepts your data in its shape and converts it to the platform's canonical shape in a defined, observable sequence. Validation happens after mapping — not at the HTTP boundary — so the receiver can stay fast and protocol-agnostic.

Flow
raw event (webhook / adapter)
      │
      ▼
raw.<tenant>.<adapter_kind>.v1        ← raw topic, payload kept verbatim
      │
      ▼
normalisation (operator-configured field mapping / DSL)
      │
      ▼
canonical schema validation (Avro, from the schema registry)
      │
      ├── valid ───►  canonical.<type>.v1   ← canonical topic, Avro-encoded
      │
      └── invalid ─►  DLQ (dead-letter)      ← quarantined for inspection
  1. Accept & land raw. The receiver (or adapter) publishes the payload verbatim to a per-tenant, per-adapter raw topic — raw.<tenant>.<adapter_kind>.v1. Nothing is dropped; the raw record is durable.
  2. Normalise. The normaliser applies the adapter's field mapping to translate the source shape into a canonical event of one of the eleven types.
  3. Validate. The canonical event is validated against its Avro schema from the schema registry. Schemas are managed centrally; mappings are validated against them.
  4. Route. Valid events go to the canonical topic (canonical.<type>.v1, Avro-encoded) where the enrichment and detection engine consumes them. Events that fail validation are routed to a dead-letter queue for inspection rather than discarded.
Schema-first, validated downstream. Canonical schemas live in the schema registry as Avro and are the contract the normaliser enforces. The HTTP receiver does not validate your payload against Avro at ingest time — it forwards raw bytes — so a malformed payload is accepted at the edge (202) and surfaces later in the DLQ, not as an HTTP error. Watch the DLQ during integration.

Onboarding

How a tenant goes from contract to live ingestion.

Registration handshake

Because the databus is operator-provisioned, the artefacts you need are handed to you — you do not generate them. A typical webhook-adapter onboarding produces:

  1. Your tenant slug and one or more adapter slugs (the {tenant}/{adapter} segments on the receiver path).
  2. The per-adapter HMAC secret, seeded in Vault and shared out-of-band (encrypted channel — never plaintext).
  3. The ingestion host your environment is reachable on (see endpoint exposure — it is environment-specific and not published here).
  4. The canonical type(s) your adapter maps to, and the agreed field mapping from your source shape.
  5. For non-webhook protocols (ISO 8583, ISO 20022, Kafka, REST-poll, SQL-tail, SFTP): the connection details the operator needs from your side.

A shadow-mode period typically follows — your events flow in and are normalised and validated, but run alongside existing controls before any decision traffic depends on them. Watch the DLQ during this window to confirm your mappings produce schema-valid canonical events.

Endpoint exposure

Public exposure is deployment-specific — this page publishes no base URL. The receiver runs as an internal databus-ingest service. Whether — and on which host — it is reachable from outside the platform depends on the deployment's gateway configuration. Where it is exposed, it must be reached only through the approved gateway and domain shared at onboarding (the https://<ingest-host> placeholder used in the examples on this page), and never by addressing an internal service port directly. If your environment does not expose the receiver publicly, ingestion runs over the provisioned adapter protocols instead. Confirm the exact host and exposure model with your onboarding contact.
  • Always present the secret over TLS; the platform refuses cleartext transport.
  • Treat the HMAC secret like any production credential — store it in your secret manager, never in source control, and mask it in logs.
  • The path /api/v1/databus/webhook/{tenant}/{adapter} is stable; only the host varies by environment.

Support

The databus is an enterprise surface — onboarding and adapter configuration are handled with your integration contact, not through self-service. For the synchronous decision API, key management, decisions, customers and outbound webhooks, see the main REST API reference. Quote your tenant slug and, where available, the event_id from a 202 on any support thread.

Where this fits /developers is the self-serve reference for real-time decisions (/evaluate/{channel}). This page is the enterprise ingestion gateway for banks, switches, core-banking systems and message buses feeding data — transactional and non-transactional — into the platform.