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.
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.
/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
Synchronous decisions
Real-time per-transaction scoring via /api/v1/evaluate/{channel} — or the channel-agnostic /api/v1/transactions/ingest alias. API-key auth, decision returned inline. Documented in the main API reference.
Databus ingestion
Streaming / bulk collection of transaction and non-transaction data points through provisioned adapters. One HMAC-signed REST receiver; everything else is adapter-driven.
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.
/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
{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:
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.
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:
POSTonly. 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/jsonby default, orapplication/xmlwhen 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:
# 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) )
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": "..."}
# Bash — sign "<timestamp>.<body>" with the per-adapter secret
SECRET='<per-adapter-hmac-secret-from-onboarding>'
INGEST='https://<ingest-host>' # provided at onboarding — do NOT guess
BODY='{"event":"account.updated","account_number":"0123456789"}'
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: evt-2026-06-07-000123" \
--data "$BODY"
hmac.compare_digest in Python).
Request headers
| Header | Required | Meaning |
|---|---|---|
| X-Timestamp | yes | Unix 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-Signature | yes | sha256=<hex> — the lowercase-hex HMAC-SHA256 of "<X-Timestamp>.<body>". The sha256= prefix is required; a header without it fails verification. |
| X-Idempotency-Key | no | Opaque 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-Hint | no | Optional 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-Type | recommended | application/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.
{
"event_id": "550e8400-e29b-41d4-a716-446655440000"
}
| Status | Body | Meaning |
|---|---|---|
| 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. |
| 401 | — | Signature failed, timestamp outside the 5-minute window, or the timestamp header was unparseable. No distinguishing detail by design. |
| 404 | — | Unknown {tenant}/{adapter} — the pair is not provisioned. |
| 405 | — | Method other than POST. |
| 413 | — | Body exceeds the 1 MiB limit. |
| 429 | — | Per-adapter rate limit exceeded. Carries Retry-After. Default ceiling is 100 requests/second per adapter unless your contract sets otherwise. |
| 500 | — | The platform could not durably publish the event. Safe to retry with the same idempotency key. |
Idempotency & replay protection
- Send a stable
X-Idempotency-Keyderived 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-Hintto 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.
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
| Protocol | Direction | Typical source | How it's exposed |
|---|---|---|---|
| Inbound webhook | Source → platform (push) | Event-emitting apps, gateways | Public REST receiver (HMAC-signed) |
| ISO 8583 | Source → platform | POS / ATM switch traffic | Provisioned adapter — not public REST |
| ISO 20022 | Source → platform | RTGS, SWIFT, NIBSS Instant Payments | Provisioned adapter — not public REST |
| Kafka | Platform consumes | Your existing event bus / topics | Provisioned consumer adapter |
| REST polling | Platform polls source | Core-banking / vendor REST APIs | Provisioned poll adapter (you expose the source API to the platform, not vice-versa) |
| SQL tail-ingestion | Platform reads source | Core-banking database | Provisioned read-only SQL tail adapter |
| SFTP file drops | Source → platform | Batch exports, EOD files | Provisioned SFTP adapter (you drop files; the platform ingests them) |
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 type | What it represents |
|---|---|
| transaction | A monetary or value-bearing event across any channel (POS, ATM, NIP, RTGS, USSD, mobile, wallet, …). The richest type; mirrors the synchronous /evaluate payload. |
| account | An account record or change — open / close, status, Post-No-Debit holds, balances, branch. |
| customer | A customer / CIF profile and its updates — identity, KYC tier, screening status, PEP flag. |
| device | Device telemetry and bindings — device IDs, OS / browser / app fingerprints, first-seen. |
| session | A channel session — login, session lifecycle, the activity grouping that stitches multiple actions together. |
| related_party | Corporate control-structure parties — directors, shareholders, beneficial owners, signatories. |
| merchant | Merchant master data — merchant identity, category (MCC), location. |
| terminal | Terminal / acceptance-device registry — POS terminals, ATM registry, location, deployment type. |
| name_enquiry | Account name-enquiry results — the NIP / inter-bank lookups that resolve a NUBAN to an account name. |
| blocklist | Blocklist / watchlist / allowlist feed entries — entities you want the list checker to act on. |
| adverse_media | Adverse-media and screening intelligence — negative-news and watch signals attached to an entity. |
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.
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
- 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. - Normalise. The normaliser applies the adapter's field mapping to translate the source shape into a canonical event of one of the eleven types.
- Validate. The canonical event is validated against its Avro schema from the schema registry. Schemas are managed centrally; mappings are validated against them.
- 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.
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:
- Your tenant slug and one or more adapter slugs (the
{tenant}/{adapter}segments on the receiver path). - The per-adapter HMAC secret, seeded in Vault and shared out-of-band (encrypted channel — never plaintext).
- The ingestion host your environment is reachable on (see endpoint exposure — it is environment-specific and not published here).
- The canonical type(s) your adapter maps to, and the agreed field mapping from your source shape.
- 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
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.
/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.