Skip to content

Transactions

Transactions represent card payment events associated with members. Ingesting transaction data enables the platform to match purchases against active deals, trigger cashback rewards, and track loyalty program progress.

The publisher software will be continuously syncing anonymized records of all ingested transaction data to the central K42 Platform for routing.

Transaction Format

Each transaction event represents a card payment and includes the following fields:

FieldTypeRequiredDescription
member_idstringyesThe member this transaction belongs to
external_idstringyesYour unique identifier for this transaction
auth_codestringyesAuthorization code from the issuing bank
networkstringyesCard network: "visa", "mastercard", "amex", or "other"
binstringyesBank Identification Number (first 6-8 digits of card)
merchant_idstringyesMerchant identifier at the payment processor
statement_descriptorstringnoStatement descriptor from the merchant
amountintegernoTransaction amount in minor units (e.g. cents)
currencystringnoISO 4217 currency code (e.g. "GBP", "USD")
authed_atstringyesISO 8601 timestamp of when the card was authorized
cleared_atstringnoISO 8601 timestamp of when the transaction settled
reverted_atstringnoISO 8601 timestamp of when the transaction was refunded

Amounts

All monetary amounts are expressed in minor units (the smallest denomination of the currency). For example:

  • GBP 25.99 → 2599
  • USD 100.00 → 10000

Ingesting Transactions

Send transaction events in batches via the ingest endpoint:

bash
curl -X POST https://<your-instance-url>/publisher.v1/transaction/ingest \
  -H "Authorization: Bearer <token>" \
  -H "Content-Type: application/json" \
  -d '{
    "events": [
      {
        "member_id": "m_x1y2z3",
        "external_id": "txn_98765",
        "auth_code": "AUTH123",
        "network": "visa",
        "bin": "411111",
        "merchant_id": "MID_STORE_01",
        "statement_descriptor": "ACME Co",
        "authed_at": "2024-01-15T10:30:00Z"
      }
    ]
  }'

Transaction Lifecycle

Transactions follow a lifecycle from authorization through to settlement or refund:

authorized  →  cleared (settled)
            →  reverted (refunded)

You can and should ingest transactions as soon as the card authorization occurs (with only authed_at set), then update it later when it clears or is reverted.

To update the transaction simply resubmit it with the additional recorded data making sure you reference the same external_id. For example, to mark a transaction as cleared (settled) set the cleared_at to the relevant timestamp:

json
{
  "member_id": "m_x1y2z3",
  "external_id": "txn_98765",
  "authed_at": "2024-01-15T10:30:00Z",
  ...
  "cleared_at": "2024-01-16T08:00:00Z"
}

Just keep in mind that:

  • A transaction that has been cleared cannot be reverted.
  • A transaction that has been reverted cannot be cleared.

Attempting an invalid state transition results in an error.

Sending Finalized Data Directly

While you should send pre-finalized transaction data as soon as possible, you can also just ingest the completed event in one go with both authed_at and cleared_at or reverted_at set.

Idempotency

Transactions are deduplicated by external_id. Sending the same transaction event multiple times will not create duplicates, making it safe to retry failed requests.

Choosing an external_id

The external_id should be a unique identifier you generate for each transaction. The format of this identifier has a significant impact on ingestion performance so care should be taken here.

Use a time-sortable format like UUIDv7. Time-sortable identifiers are monotonically increasing which makes them efficient for BTree indexes - inserts and range scans perform well because new values are always appended near the end of the index rather than causing random page splits.

Avoid purely random identifiers (e.g. UUIDv4) as they fragment indexes over time and degrade write and query performance at scale.

What Gets Shared with the Platform

Transaction data you ingest is not sent verbatim to the K42 platform. The software is designed to protect you and your members' financial privacy.

Transaction Identifiers

Instead of forwarding raw card details, the Publisher computes transaction identifiers - one-way SHA-256 hashes derived from a combination of payment fields:

  • Authorization code (auth_code)
  • Card network (network)
  • Bank Identification Number (bin)
  • Merchant identifier (merchant_id)

Multiple transaction identifiers may be computed containing a variety of combinations.

These identifiers allow the platform to correlate your publisher-side transactions with retailer-side payment records without either party exposing raw card data.

What the Platform Receives

DataNotes
Transaction identifiers (SHA-256 hashes)Opaque, irreversible
Merchant IDRequired for routing to the correct merchant
Statement DescriptorRequired for routing to the correct merchant
Timestamps (authed_at, cleared_at, reverted_at)For lifecycle tracking and matching
Associated deal clip IDsFor cashback processing
Loyalty program enrollment IDsFor stamp tracking

How Matching Works

Software running within the retailer independently computes the same hashes from their payment card details received from their acquirer.

The platform routes transactions to the relevant retailer using the provided mid and possibly the statement_descriptor if the MID is not enough to disambiguate.

The retailer-side software then correlates transactions by finding identical hash values from both sides. When a match is found and the transaction is associated with a clipped deal or loyalty program, the platform triggers the appropriate reward (cashback credit, stamp collection progress, etc.).

This design ensures that transaction matching works without any party having access to another party's raw payment data.

Transaction Lifecycle

After you ingest a transaction, it flows through the system and can trigger events that are communicated back to you via webhooks:

You ingest transaction
  → Publisher computes transaction identifiers
  → Platform routes to retailer for matching
  → Match found against clipped deal or loyalty program
  → Platform triggers reward and sends webhook event

Cashback Rewards

When a transaction matches a clipped deal:

  1. cashback.created - A cashback reward is triggered. You receive the amount, currency, and a reference back to the originating transaction via transaction_ref (your external_id).
  2. cashback.cleared - The cashback settles (when the underlying transaction clears).
  3. cashback.reverted - The cashback is reversed (if the underlying transaction is refunded).

Loyalty Program Progress

When a transaction matches a loyalty program:

  1. qualifying_purchase.created - The transaction counts as a stamp toward the member's enrolled loyalty program.
  2. qualifying_purchase.voided - The stamp is voided (e.g. due to a refund).

When enough stamps accumulate to meet the program's threshold, a redemption is triggered automatically and a cashback.created event is sent with a loyalty-program-redemption source.

Correlating Events to Your Data

All webhook events include a transaction_ref field that corresponds to the external_id you provided when ingesting the transaction. Use this to correlate platform events back to your internal transaction records.

See the Cashback Events and Loyalty Events documentation for the full event payload reference.