Card Transactions

Send card-purchase events into Findity to auto-generate expenses for cardholders.

This guide covers the partner-side integration for card issuers β€” banks, fintechs, and Card-as-a-Service platforms β€” that want to embed Findity's expense management. You'll onboard a customer with a one-time setup call, then stream card transactions so each purchase becomes an expense in the cardholder's app, ready for receipt capture and approval.

Overview

A card-issuer integration has two phases β€” a one-time customer onboarding flow and a continuous transaction stream. The layers below sit between your existing card-issuing platform and the Findity expense engine:

    β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
    β”‚       Your card-issuing platform      β”‚
    β”‚   (customer portal + card scheme      β”‚
    β”‚    authorization / settlement feed)   β”‚
    β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
    β”‚         Your Findity adapter          β”‚
    β”‚  (onboarding calls + transaction      β”‚
    β”‚   streaming + invoice reconciliation) β”‚
    β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
    β”‚           Findity APIs                β”‚
    β”‚   Admin API Β· Connect /cardtxns       β”‚
    β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
    β”‚       Findity Authentication          β”‚
    β”‚       (OAuth 2.0 access token)        β”‚
    β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

Onboarding runs once per customer (creating the organization, the cardholders, and at least one administrator). After that, every card authorization and every settlement on a connected card flows through a single POST /cardtransactions call. Transactions are processed asynchronously by default β€” the endpoint returns 202 Accepted immediately and queues the transaction for processing. The closer to real-time you send them, the better the cardholder experience β€” expenses appear in the app within seconds of purchase.

Prerequisites

  1. Have Findity configure your cardType value. Contact your project manager or Findity support before development begins β€” cardType must match a value the backend recognises, otherwise every transaction returns 400.
  2. Complete Authentication and have an admin-scoped access token. The same token is used for onboarding calls (Admin API) and transaction calls (Connect API).
  3. Decide which liability each card type you issue carries: PRIVATE, CORPORATE, or DEBET. This is not a per-transaction choice β€” it's tied to the card and dictates how Findity treats the resulting expenses. See Key concepts below.

Key concepts

ConceptDescription
cardTypeAn identifier Findity provisions for your card brand (e.g. acme-bank-corporate). Used to route the transaction through the right pipeline. Cannot be invented by the integrator β€” must be configured by Findity in advance.
liabilityWho pays the card's invoice. PRIVATE (cardholder pays, employer reimburses), CORPORATE (employer pays directly, no reimbursement), or DEBET (corporate bank account is charged directly, similar to corporate but for debit-card products).
eventTypeThe transaction's lifecycle stage from the card scheme: AUTHORIZATION_ACCEPTED (initial purchase), AUTHORIZATION_REJECTED (declined), AUTHORIZATION_REVERSAL (cancelled before settlement), CAPTURE (settlement completed), REFUND (returned), PRE_AUTHORIZATION (preliminary hold, hidden from cardholder until confirmed).
supplierCardIdYour unique identifier for the physical or virtual card. Findity uses it to look up the cardholder on subsequent transactions and to group same-card history.
transactionIdYour unique identifier for the transaction. Required on every call β€” Findity uses it to deduplicate retries and to match AUTHORIZATION_ACCEPTED events to their later CAPTURE updates.
invoiceNumberSet on CAPTURE events. Findity groups all CORPORATE-liability transactions that share the same invoiceNumber into a single expense report, ready for the administrator to review against the card-issuer invoice.

API endpoints

Base URL for all examples in this guide:

https://stage-expense.findity.com

Use the matching production host (https://expense.findity.com) only after your integration is verified in stage.

Create customer organization

POST /api/admin/organizations/externalid/{externalId}
ParameterTypeDescription
externalIdstringYour unique identifier for the customer organization

See the Admin API β€” Organizations guide for full request and response details.

Create cardholder or administrator

PUT /api/admin/organizations/externalid/{externalId}/users
ParameterTypeDescription
externalIdstringYour unique identifier for the customer organization

See the Admin API β€” Users guide for full request and response details.

Submit card transaction

POST /v1/connect/cardtransactions

This is the primary endpoint for this integration β€” used for all transaction lifecycle events.

Required headers

HeaderValueApplies to
Content-Typeapplication/jsonall three endpoints
AuthorizationBearer {access_token}all three endpoints
X-Findity-ApiVersion3Admin API calls only (organizations, users). The Connect API does not version this way.

Customer onboarding

When a customer signs up to use Findity through your card-issuing platform β€” typically from a "My Pages" or equivalent surface β€” your adapter makes three sequential calls to provision the customer in Findity.

    Customer activates expense management
    in your card-issuing platform
                       β”‚
                       β–Ό
    β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
    β”‚  Card issuer β†’ Findity, three sequential    β”‚
    β”‚  Admin API calls:                           β”‚
    β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
    β”‚  1.  POST /api/admin/organizations/         β”‚
    β”‚      externalid/{externalId}                β”‚
    β”‚      β†’ Create the customer's organization   β”‚
    β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
    β”‚  2.  PUT /api/admin/organizations/          β”‚
    β”‚      externalid/{externalId}/users          β”‚
    β”‚      β†’ Add each cardholder as a user        β”‚
    β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
    β”‚  3.  PUT /api/admin/organizations/          β”‚
    β”‚      externalid/{externalId}/users          β”‚
    β”‚      β†’ Add at least one administrator       β”‚
    β”‚        (set `admin: true` on the user)      β”‚
    β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

After these three calls succeed, the customer's organization is provisioned, its cardholders exist as users, and there is at least one administrator who can sign in to the Findity admin web client.

The administrator is who reviews corporate-card invoices, runs reports, and configures expense categories. The administrator does not need to be a cardholder, and a cardholder may also be made an administrator β€” set admin: true on the user object.

See the Organizations and Users guides for the full request and response shape of these calls.

πŸ“˜

The organizationId and personId values returned by these onboarding calls are what you'll pass into every subsequent POST /cardtransactions call. Store them in your adapter, keyed by your own customer and cardholder ids.

Add card to user

A user becomes a cardholder when a card is linked to their account. An employee can hold multiple cards (for example, a personal-liability card for travel plus a corporate-liability card for office purchases), but most integrations connect one card per cardholder.

The card's liability setting dictates how Findity treats the expenses generated from it. Pick the right value when the card is provisioned β€” changing it later requires migration.

liabilityWho pays the card invoiceExpense behaviourCardholder can delete expenses
PRIVATECardholder, employer reimbursesTreated like any out-of-pocket purchase. Cardholder submits a report and is reimbursed via payroll or expense payout.Yes β€” cardholders may delete a generated expense if the purchase was private.
CORPORATEEmployer is invoiced directlyCardholder is not reimbursed. Expenses cannot be submitted individually β€” they are grouped by invoiceNumber after settlement and the cardholder submits the resulting corporate-card report.No β€” corporate-card expenses are part of the audit trail and cannot be removed.
DEBETCorporate bank account is chargedSimilar to CORPORATE β€” no reimbursement, the transaction flows straight to bookkeeping. Use for debit-card products tied to an employer account.No.
⚠️

The cardholder's user must exist in the organization (created in the onboarding step) before any transaction can be associated with them. A transaction with an unknown personId returns 400.

Integration steps

After onboarding, your adapter spends most of its time on a single endpoint β€” POST /api/v1/connect/cardtransactions β€” with the eventType field driving the behaviour. The four steps below cover the full transaction lifecycle. Steps 1 and 3 fire on every successful transaction; steps 2 and 4 are conditional. Reuse the same transactionId across all events that belong to one purchase so Findity can stitch them together.

Step 1 β€” Send the authorization

When the card scheme approves a purchase, send an AUTHORIZATION_ACCEPTED event so Findity creates the draft expense and notifies the cardholder via the mobile app. Generate a fresh transactionId here and reuse it on every subsequent event for this purchase.

POST /api/v1/connect/cardtransactions
{
  "eventType": "AUTHORIZATION_ACCEPTED",
  "transactionId": "<your-unique-id>",
  ...
}

Optional pre-step β€” pre-authorization. For flows where the final amount isn't known yet (e.g. fuel pumps, hotel holds), send PRE_AUTHORIZATION first. Findity creates a draft expense that's hidden from the cardholder. Replay the same transactionId with AUTHORIZATION_ACCEPTED once the final amount is confirmed; the existing draft is updated and the cardholder is notified.

POST /api/v1/connect/cardtransactions
{
  "eventType": "PRE_AUTHORIZATION",
  "transactionId": "<your-unique-id>",
  ...
}

See Generate expenses from card transaction for the full request body, the required fields, and a worked cURL example.

Step 2 β€” Reverse or reject (if applicable)

If the card scheme cancels the authorization before settlement, or rejects it outright, replay the same transactionId with the matching eventType.

POST /api/v1/connect/cardtransactions
{
  "eventType": "AUTHORIZATION_REVERSAL",
  "transactionId": "<same-id-as-step-1>",
  "billingAmount": <reduced-amount>,
  ...
}
POST /api/v1/connect/cardtransactions
{
  "eventType": "AUTHORIZATION_REJECTED",
  "transactionId": "<same-id-as-step-1>",
  ...
}

Findity reduces the amount on AUTHORIZATION_REVERSAL and removes the draft expense entirely on AUTHORIZATION_REJECTED. After either event, the lifecycle is closed for this transactionId and no further events (except REFUND after a settled CAPTURE) are accepted.

Step 3 β€” Capture on settlement

When the transaction settles (the funds actually move from the cardholder's account), send a CAPTURE event with the same transactionId. For CORPORATE-liability cards, include an invoiceNumber β€” Findity groups all transactions sharing the same invoiceNumber into a single corporate-card report that the cardholder submits as one batch.

POST /api/v1/connect/cardtransactions
{
  "eventType": "CAPTURE",
  "transactionId": "<same-id-as-step-1>",
  "invoiceNumber": "INV-2026-05",
  "billingAmount": <final-amount>,
  ...
}

CAPTURE finalises the expense amount. If the cardholder hasn't yet attached a receipt, they're prompted to do so at this point. See Invoice matching for the full grouping behaviour.

Step 4 β€” Process refunds (if applicable)

When a refund clears for a previously captured transaction, send a REFUND event using the same transactionId. Findity records the negative amount on the original expense and preserves the audit trail β€” the cardholder can see both the original purchase and the refund in their report.

POST /api/v1/connect/cardtransactions
{
  "eventType": "REFUND",
  "transactionId": "<same-id-as-step-1>",
  "billingAmount": <refund-amount>,
  ...
}

For partial refunds, send the partial amount; Findity adjusts the net total accordingly. For full refunds, send the full original amount as billingAmount β€” do not negate the value, the negation is implicit from eventType: REFUND.

πŸ’‘

The whole lifecycle uses a single endpoint with the same body shape β€” only eventType and the relevant amount fields change. Build one transport function in your adapter and switch on eventType at the boundary.

Generate expenses from card transaction

For each purchase made on a connected card, your adapter sends a POST /cardtransactions request. Findity creates a draft expense for the cardholder, sends them a push notification through the Findity mobile app, and lets them attach a receipt and submit it for approval. The same endpoint is used for the full transaction lifecycle β€” initial authorization, optional reversal, settlement (CAPTURE), and refund β€” by varying the eventType field and reusing the same transactionId.

    Card issuer                       Findity
    ────────────────                  ─────────────────────────

    Cardholder
    makes a purchase
          β”‚
          β”‚   POST /cardtransactions
          β”‚     eventType:
          β”‚     AUTHORIZATION_ACCEPTED
          β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β–Ί   Receive transaction
          β”‚                                          β”‚
          β”‚                                          β–Ό
          β”‚                                  Create draft expense
          β”‚                                  for the cardholder
          β”‚                                          β”‚
          β”‚       Push notification                  β”‚
          ◄───────────────────────────────────────────
          β”‚                                          β”‚
          β–Ό                                          β–Ό
    Settlement clears                         Cardholder attaches
          β”‚                                   a receipt and submits
          β”‚
          β”‚   POST /cardtransactions
          β”‚     eventType: CAPTURE
          β”‚     invoiceNumber: <inv>
          β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β–Ί   Update the expense;
          β”‚                                  if liability is
          β”‚                                  CORPORATE, group it
          β”‚                                  with siblings into
          β”‚                                  one report keyed by
          β”‚                                  invoiceNumber

Request body β€” required fields

FieldTypeDescription
cardTypestringYour card brand, configured by Findity in advance (see Prerequisites).
eventTypestringOne of AUTHORIZATION_ACCEPTED, AUTHORIZATION_REJECTED, AUTHORIZATION_REVERSAL, CAPTURE, REFUND, PRE_AUTHORIZATION.
supplierCardIdstringYour unique identifier for the card.
transactionIdstringYour unique identifier for the transaction. Reuse the same value across the full lifecycle (authorization β†’ capture). For Mastercard, you may alternatively use clearingUniqueId.
transactionDatestring (ISO 8601)When the purchase occurred.
billingAmountnumberAmount billed to the cardholder, in their billing currency. Maximum value: 1,000,000,000.
billingCurrencyCodestringISO 4217 code for billingAmount (e.g. SEK).

Request body β€” conditionally required fields

These fields are required depending on your card type and how the cardholder is identified:

FieldTypeRequired whenDescription
liabilitystringRequired for non-real-time card types (not Mastercard/Visa)One of PRIVATE, CORPORATE, DEBET. See the table in Add card to user.
organizationIdstringRequired when identifying cardholderThe Findity organization id returned by the onboarding POST /organizations call.
personIdstringRequired with organizationId (option 1)The Findity person id of the cardholder, returned by the onboarding user create call.
employeeIdstringAlternative to personId (option 2)The Findity employee id, if you stored this instead of personId.
πŸ“˜

Cardholder identification: You must provide either organizationId + personId, or organizationId + employeeId. If the card is already registered in Findity (via a previous transaction), the system can also look up the cardholder by supplierCardId alone.

Request body β€” optional fields

FieldTypeDescription
merchantAmountnumberAmount in the merchant's currency, if different from billing currency. Maximum value: 1,000,000,000.
merchantCurrencyCodestringISO 4217 code for merchantAmount.
billingExchangeRatenumberFX rate applied to convert merchant amount to billing amount. Must be positive. Maximum value: 1,000,000,000.
billingVatAmountnumberVAT portion of billingAmount, when known. Maximum value: 1,000,000,000.
merchantMccnumberMerchant Category Code (4-digit).
posNamestringThe merchant name as it appears on the receipt. Surfaced on the expense for the cardholder.
descriptionstringFree-form description, surfaced alongside posName on the expense.
cardIssuerstringThe issuing institution name (useful when one cardType covers multiple sub-brands). If not provided, defaults to cardType.
cardNumberstringMasked PAN (e.g. XXXXXXXXXXXX6789). Last four are usable for cardholder identification; full PAN must not be sent.
countrystringISO 3166-1 alpha-2 or alpha-3 country code of the merchant. Numeric country codes are also accepted for Mastercard/Visa.
citystringMerchant city.
statestringMerchant state or region. Values starting with -- are ignored.
transactionReferencestringA second identifier from your card scheme (for example, the original network reference) when transactionId alone isn't enough to trace.
transactionOwnerNamestringDisplay name of the cardholder. Findity shows this in the admin overview alongside the cardholder's user record. If not provided, defaults to the cardholder's name from their user profile.
invoiceNumberstringSet on CAPTURE events. Groups all matching transactions into one corporate-card report (see Invoice matching).
clearingUniqueIdstringAlternative to transactionId for Mastercard transactions.
vatstringOrganization VAT number. Used as fallback to identify the organization if organizationId is not provided.
employeeNumberstringEmployee number for reference.
contractIDstringContract identifier from your card scheme. If not provided, defaults to supplierCardId.
authorizationIdstringAuthorization identifier from the card network.
approvalCodestringApproval code from the card network.
isSimulatedbooleanSet to true for test/demo transactions. Default: false.

Example request

cURL

curl -X POST "https://stage-expense.findity.com/api/v1/connect/cardtransactions" \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer YOUR_ACCESS_TOKEN" \
  -d '{
    "cardType": "acme-bank-corporate",
    "posName": "CafΓ© Solde",
    "description": "Espresso, takeaway",
    "billingAmount": 250,
    "billingCurrencyCode": "SEK",
    "merchantAmount": 24,
    "merchantCurrencyCode": "EUR",
    "merchantMcc": 5921,
    "transactionDate": "2026-05-22T12:41:00Z",
    "transactionId": "464411049",
    "eventType": "AUTHORIZATION_ACCEPTED",
    "organizationId": "8a5c9cc090e8ccae0190edd18eca07d3",
    "personId": "8a5c848990e8cbb70190edd6cea907ba",
    "transactionOwnerName": "Bob Curry",
    "supplierCardId": "721c47e6-ba56-4c1f-82a2-1d3dbf8c81f2",
    "liability": "PRIVATE",
    "cardNumber": "XXXXXXXXXXXX6789"
  }'

Example response

By default, transactions are processed asynchronously. A successful call returns 202 Accepted with the request body echoed back, indicating the transaction has been queued for processing.

Async response (default):

{
  "cardType": "acme-bank-corporate",
  "posName": "CafΓ© Solde",
  "description": "Espresso, takeaway",
  "billingAmount": 250,
  "billingCurrencyCode": "SEK",
  "merchantAmount": 24,
  "merchantCurrencyCode": "EUR",
  "merchantMcc": 5921,
  "transactionDate": "2026-05-22T12:41:00Z",
  "transactionId": "464411049",
  "eventType": "AUTHORIZATION_ACCEPTED",
  "organizationId": "8a5c9cc090e8ccae0190edd18eca07d3",
  "personId": "8a5c848990e8cbb70190edd6cea907ba",
  "transactionOwnerName": "Bob Curry",
  "supplierCardId": "721c47e6-ba56-4c1f-82a2-1d3dbf8c81f2",
  "liability": "PRIVATE",
  "cardNumber": "XXXXXXXXXXXX6789"
}

Sync response (with ?async=false):

For integration testing, you can add ?async=false to process the transaction synchronously. This returns 201 Created with the stored transaction(s), including the Findity-assigned id and expenseRecordId:

[
  {
    "id": "9a3b14f0a0d540e191f6c3d2b8ea1190",
    "expenseRecordId": "7f2c4d1a4b3e44e58b21e5c7f6d9aa01",
    "cardType": "acme-bank-corporate",
    "posName": "CafΓ© Solde",
    "description": "Espresso, takeaway",
    "billingAmount": 250,
    "billingCurrencyCode": "SEK",
    "merchantAmount": 24,
    "merchantCurrencyCode": "EUR",
    "merchantMcc": 5921,
    "transactionDate": "2026-05-22T12:41:00Z",
    "transactionId": "464411049",
    "eventType": "AUTHORIZATION_ACCEPTED",
    "organizationId": "8a5c9cc090e8ccae0190edd18eca07d3",
    "personId": "8a5c848990e8cbb70190edd6cea907ba",
    "transactionOwnerName": "Bob Curry",
    "supplierCardId": "721c47e6-ba56-4c1f-82a2-1d3dbf8c81f2",
    "liability": "PRIVATE",
    "cardNumber": "XXXXXXXXXXXX6789",
    "lastUpdated": "2026-05-22T12:41:01.482Z"
  }
]
⚠️

Production note: Do not use ?async=false in production β€” it may cause issues with simultaneous requests. Use it only for integration testing.

⏱️

Send AUTHORIZATION_ACCEPTED as close to real time as your card scheme allows β€” typically within a few seconds of the authorization clearing. The cardholder's push notification is generated when Findity processes the event, so any delay translates directly into a worse user experience.

Invoice matching

For CORPORATE-liability cards, the cardholder cannot submit individual expenses β€” purchases are invoiced to the employer monthly (or whatever your billing cycle is) and must be grouped to match the invoice. This is what invoiceNumber is for.

The flow:

  1. During the month, you send AUTHORIZATION_ACCEPTED events for each purchase. Findity creates draft expenses; the cardholder attaches receipts but cannot submit yet.
  2. When your card scheme settles a batch and issues an invoice, you replay each affected transaction with eventType: CAPTURE, the same transactionId as the original authorization, and the new invoiceNumber.
  3. Findity matches the CAPTURE event to the original draft by transactionId, updates the expense with the final amount, and groups all expenses sharing the same invoiceNumber into one corporate-card report.
  4. The cardholder submits the resulting report. The administrator sees an overview of open corporate-card reports and can send reminders to cardholders who haven't yet submitted.

Example CAPTURE request

cURL

curl -X POST "https://stage-expense.findity.com/api/v1/connect/cardtransactions" \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer YOUR_ACCESS_TOKEN" \
  -d '{
    "cardType": "acme-bank-corporate",
    "transactionId": "464411049",
    "eventType": "CAPTURE",
    "invoiceNumber": "INV-2026-05",
    "billingAmount": 250,
    "billingCurrencyCode": "SEK",
    "transactionDate": "2026-05-22T12:41:00Z",
    "organizationId": "8a5c9cc090e8ccae0190edd18eca07d3",
    "personId": "8a5c848990e8cbb70190edd6cea907ba",
    "supplierCardId": "721c47e6-ba56-4c1f-82a2-1d3dbf8c81f2",
    "liability": "CORPORATE"
  }'
πŸ’‘

You can update the same transactionId multiple times β€” for example, an AUTHORIZATION_REVERSAL followed by a CAPTURE for the corrected amount. Always send the same transactionId across the lifecycle so Findity can stitch the events together.

Transaction lifecycle

Each transaction progresses through a lifecycle of events. The eventType field indicates the current stage:

    PRE_AUTHORIZATION ──► AUTHORIZATION_ACCEPTED ──► CAPTURE
          β”‚                       β”‚                     β”‚
          β”‚                       β–Ό                     β–Ό
          β”‚              AUTHORIZATION_REVERSAL      REFUND
          β”‚                       β”‚
          β–Ό                       β–Ό
    AUTHORIZATION_REJECTED   (expense removed)

Event type behavior

eventTypeExpense createdCardholder notifiedCan be followed by
PRE_AUTHORIZATIONYes (hidden)NoAUTHORIZATION_ACCEPTED, AUTHORIZATION_REJECTED
AUTHORIZATION_ACCEPTEDYes (visible)Yes (push notification)AUTHORIZATION_REVERSAL, CAPTURE, REFUND
AUTHORIZATION_REJECTEDNo (removed if existed)NoNone
AUTHORIZATION_REVERSALUpdated (amount reduced)NoCAPTURE
CAPTUREUpdated (finalized)NoREFUND
REFUNDUpdated (negative amount)NoNone

Capture timeout

If a CAPTURE event is not received within a configured timeout period (set per cardType), Findity automatically removes the draft expense. This prevents orphaned expenses from purchases that were never settled.

Airline/travel accumulation

For airline and travel merchants (MCC codes 3005, 3010, 3020, 3030, 3031, 3041, 4511), multiple CAPTURE events with the same transactionId are accumulated onto the existing expense rather than creating duplicates. This handles the common pattern where airlines send multiple settlement records for a single booking.

Error handling

Findity returns a JSON envelope on errors:

{ "result": "error", "code": "<machine-readable-code>", "description": "<human-readable message>" }

Validation errors (400)

CodeDescription
INVALID_JSON_BODYThe request body could not be parsed as valid JSON.
NO_KNOWN_JSON_PROPERTIESThe request body contains no recognized fields.
MISSING_PARAMETERA required field is missing. The description field indicates which parameter. Common missing fields: cardType, supplierCardId, transactionId, eventType, billingAmount, billingCurrencyCode, transactionDate, liability (for non-real-time cards).
INVALID_PARAMETERA field has an invalid value. Common causes: invalid eventType, invalid liability, invalid currency code, negative billingExchangeRate, amount exceeds maximum (1,000,000,000).
MISSING_CARD_LICENSENo active license exists for the specified cardType in the organization. Contact Findity to activate the card license.
USER_NOT_FOUNDThe organizationId, personId, or employeeId does not exist in Findity, or the employee is not active in the specified organization. Confirm the IDs returned by the onboarding calls are what you're sending.

Authentication errors

StatusCodeDescription
401UNAUTHORIZEDAccess token is missing, expired, or invalid. Refresh the token and retry.
403FORBIDDENThe token does not have permission for the Connect API. Verify the API client is configured for this surface.

Other errors

StatusCodeDescription
202(no error)Returned when the card is not active (CARD_IS_NOT_ACTIVE). The transaction is silently dropped. This is not an error β€” it's expected behavior for deleted cards.
404NOT_FOUNDThe request path is wrong. Confirm you're calling /api/v1/connect/cardtransactions and not a misspelled variant.
429RATE_LIMIT_EXCEEDEDToo many requests in a short window. Apply exponential backoff before retrying.
500INTERNAL_SERVER_ERRORServer error. Safe to retry with backoff; if persistent, contact support with the request body, transactionId, and the response.

Transaction order errors

Findity enforces transaction lifecycle order. You cannot send an AUTHORIZATION_ACCEPTED, AUTHORIZATION_REVERSAL, or AUTHORIZATION_REJECTED event after a CAPTURE or REFUND has been recorded for the same transactionId. Attempting to do so returns a 400 error with the message "Transactions received in the wrong order".

Next steps