Skip to main content
Getting transaction states wrong = angry accountants. Here’s the exact logic.

The 4 states

StateTimingAmounts reliable?Use for accounting?
authorizedSeconds after purchase❌ May changePreview only
deletedBefore clearingN/ADelete from your system
cleared3-5 days after purchase✅ FinalYES
invoicedWhen issuer invoices✅ Final + invoice #YES

async function handleTransaction(event, payload) {
  const txId = payload.id;
  const state = payload.state; // from X-Event suffix or payload.state

  switch (event) {
    case 'card.transaction.authorized':
      // Create if not exists (upsert by id)
      await db.transactions.upsert(txId, { ...payload, status: 'pending' });
      break;

    case 'card.transaction.cleared':
    case 'card.transaction.invoiced':
      // Update existing OR create if you skipped authorized
      await db.transactions.upsert(txId, { ...payload, status: 'confirmed' });
      break;

    case 'card.transaction.deleted':
      // Only relevant if you stored authorized
      await db.transactions.delete(txId);
      break;
  }
}

The golden rules

  1. Always handle cleared — non-negotiable for accounting
  2. authorized + deleted + cleared = best UX (users see purchases instantly, deletions handled)
  3. authorized alone = you’ll miss transactions that clear without prior auth (rare but possible)
  4. Use id as primary key — same id across all states for one transaction
  5. card_issuer_reference differs per state — don’t use it as your primary key

Upsert logic detail

EventTransaction exists?Action
authorizedNoCreate
authorizedYesUpdate (amounts may change before clearing)
clearedNoCreate (you missed authorized — still ok)
clearedYesUpdate with final amounts
invoicedNoCreate
invoicedYesUpdate, set invoice_number
deletedYesDelete
deletedNoIgnore (nothing to delete)

receiptable flag

{ "receiptable": true }
If true, a digital receipt may arrive later via receipt.fetched. If false, don’t wait for one. Subscribe to receipt_fetched, transaction_true_vat, and transaction_line_items separately — they come as independent events after the transaction.

Card types

card_typeMeaning
corporateCompany card, company pays
privatePersonal card
corporate_with_private_invoiceCompany card, employee reimbursed privately
Verify with your client which card types to include. Common setup: corporate only.

VAT caveat

vat_rate and vat_amount on the transaction payload come from the card network — often wrong for restaurants, hotels, etc. For correct VAT, subscribe to transaction.true.vat — that comes from the actual merchant receipt.