Event Types

Available Events

Event NameTriggerContentSample Payload
transactionmessage.createdWhenever a new Transaction Message is registered in the Astrada systemA complete understanding of the created Transaction Message objectSee here
transaction.createdWhenever a new Transaction is created in the Astrada systemReferences to the transaction object and its related entitiesSee here
transaction.updatedWhenever a Transaction is updated in the Astrada systemReferences to the updated transaction object and its related entitiesSee here
banklink.completedWhenever a bank-link enrollment completesThe completed bank link and the bank accounts discovered under itSee here
bankaccount.state_changedWhenever a linked bank account's connection state changesThe bank account with its new and previous state, and any re-auth reasonSee here
banktransaction.createdWhenever a bank transaction is synced from a linked bank accountThe created bank transactionSee here
banktransaction.deletedWhenever a previously announced bank transaction is removedThe removed bank transaction's id and the reasonSee here
transaction.match.createdWhenever a bank transaction is matched to a card transactionThe match with its confidence, scoring reasons, and both matched sidesSee here
transaction.match.deletedWhenever a previously announced match is retractedThe retracted match and its bank and card transaction idsSee here
cardsubscription.createdWhenever a Card Subscription is created in the Astrada systemReference to the created card subscriptionSee here
cardsubscription.updatedWhenever a Card Subscription is updated in the Astrada systemReference to the updated card subscriptionSee here
networkbulkfeed.statechangedWhenever a Network Bulk Feed changes stateReference to the network bulk feed and the new and old statesSee here
enrichment.merchant.createdWhenever a Transaction Message is enriched with Merchant dataA complete understanding of the created Merchant EnrichmentSee here🧪

🧪 Experimental Feature: behavior may change.

Sample Payloads

Alongside the payload that is sent in HTTP request body (in JSON format), an HTTP header (webhook-event-type) is also present that allows to infer the event type.

transactionmessage.created

Payload is identical to the GET Transaction Message endpoint

transaction.created

Payload consists of links to the created transaction and its related entities.

{
  "_links": {
    "self": {
      "href": "/transactions/884820d4-1b11-4dfd-ab83-988f754712da"
    },
    "subaccount": {
      "href": "/subaccounts/c6d5e036-361e-4773-a514-b9496cf2d2b5"
    },
    "card": {
      "href": "/cards/4d1ff59f-4286-4209-9662-92e174193562"
    },
    "messages": [
      {
        "href": "/transaction-messages/f4b24a0a-6a37-4ce3-8845-ddf0b604a9b4"
      }
    ]
  },
  "acceptor": {
    "city": "LONDON",
    "country": "GBR",
    "mcc": "5734",
    "state": "LND"
  },
  "cardId": "4d1ff59f-4286-4209-9662-92e174193562",
  "cardholderBillingCurrency": "GBP",
  "cardholderBillingHoldAmount": 0,
  "cardholderBillingSettledAmount": 0.81,
  "createdAt": "2024-05-01T06:41:43.810Z",
  "descriptor": "Uber",
  "id": "884820d4-1b11-4dfd-ab83-988f754712da",
  "network": "MASTERCARD",
  "status": "SETTLED",
  "subaccountId": "c6d5e036-361e-4773-a514-b9496cf2d2b5",
  "supersededMessageIds": [],
  "transactionCurrency": "USD",
  "transactionHoldAmount": 0,
  "transactionSettledAmount": 1,
  "transactionType": "DEBIT_01",
  "updatedAt": "2024-05-02T08:41:43.810Z"
}

Augmenting transaction.created with a projection of transaction messages data

Transaction message data is included as part of the transaction.created webhook by default. An array containing all correlated messages is added to the transaction aggregate payload inside the _embedded field. Example:

{
  "_embedded": {
    "messages": [
      {
        "_links": {
          "self": {
            "href": "/transaction-messages/f4b24a0a-6a37-4ce3-8845-ddf0b604a9b4"
          }
        },
        "approvalCode": "537254",
        "cardholderBillingAmount": 1.18,
        "cardholderBillingCurrency": "GBP",
        "dateTime": "2024-04-24T13:39:45",
        "id": "f4b24a0a-6a37-4ce3-8845-ddf0b604a9b4",
        "messageType": "FINL_ADVC",
        "network": "MASTERCARD",
        "result": "APPROVED",
        "superseded": false,
        "transactionAmount": 1.45,
        "transactionCurrency": "USD",
        "transactionReference": "IWVQ22_2024-04-24"
      }
    ]
  }
}

This expanded payload is enabled by default. If your integration cannot accept the _embedded projection, reach out to your Astrada Account Manager or [email protected] to have it disabled.

transaction.updated

Payload consists of links to the updated transaction and its related entities.

{
  "_links": {
    "self": {
      "href": "/transactions/884820d4-1b11-4dfd-ab83-988f754712da"
    },
    "subaccount": {
      "href": "/subaccounts/c6d5e036-361e-4773-a514-b9496cf2d2b5"
    },
    "card": {
      "href": "/cards/4d1ff59f-4286-4209-9662-92e174193562"
    },
    "messages": [
      {
        "href": "/transaction-messages/f4b24a0a-6a37-4ce3-8845-ddf0b604a9b4"
      },
      {
        "href": "/transaction-messages/934f8701-53e3-4a2b-a000-6bb52195d2ea"
      }
    ]
  },
  "acceptor": {
    "city": "LONDON",
    "country": "GBR",
    "mcc": "5734",
    "state": "LND"
  },
  "cardId": "4d1ff59f-4286-4209-9662-92e174193562",
  "cardholderBillingCurrency": "GBP",
  "cardholderBillingHoldAmount": 0,
  "cardholderBillingSettledAmount": 0.81,
  "createdAt": "2024-05-01T06:41:43.810Z",
  "descriptor": "Uber",
  "id": "884820d4-1b11-4dfd-ab83-988f754712da",
  "network": "MASTERCARD",
  "status": "SETTLED",
  "subaccountId": "c6d5e036-361e-4773-a514-b9496cf2d2b5",
  "supersededMessageIds": [],
  "transactionCurrency": "USD",
  "transactionHoldAmount": 0,
  "transactionSettledAmount": 1,
  "transactionType": "DEBIT_01",
  "updatedAt": "2024-05-02T08:41:43.810Z"
}

Augmenting transaction.updated with a projection of transaction messages data

Transaction message data is included as part of the transaction.updated webhook by default. An array containing all correlated messages is added to the transaction aggregate payload inside the _embedded field. Example:

{
  "_embedded": {
    "messages": [
      {
        "_links": {
          "self": {
            "href": "/transaction-messages/f4b24a0a-6a37-4ce3-8845-ddf0b604a9b4"
          }
        },
        "approvalCode": "537254",
        "cardholderBillingAmount": 1.18,
        "cardholderBillingCurrency": "GBP",
        "dateTime": "2024-04-24T13:39:45",
        "id": "f4b24a0a-6a37-4ce3-8845-ddf0b604a9b4",
        "messageType": "FINL_ADVC",
        "network": "MASTERCARD",
        "result": "APPROVED",
        "superseded": false,
        "transactionAmount": 1.45,
        "transactionCurrency": "USD",
        "transactionReference": "IWVQ22_2024-04-24"
      }
    ]
  }
}

This expanded payload is enabled by default. If your integration cannot accept the _embedded projection, reach out to your Astrada Account Manager or [email protected] to have it disabled.

Superseded messages: reconciling the message list against settled amounts

Card networks sometimes deliver the same clearing more than once (a real-time advice and a bulk-file clearing, or two feeds covering the same card), and a provisional bank-sourced settlement is replaced when the network's own clearing arrives. Aggregation counts each such movement once: the duplicate's financial contribution is superseded, but its message stays listed in _links.messages (and in the _embedded.messages[] projection) as an audit trail.

Two fields make that visible:

  • supersededMessageIds on the transaction payload (and on GET /transactions/{transactionId}): the ids whose amounts do not count toward the settled totals. Always a subset of _links.messages. Every event carries the full current set (a snapshot, not a delta), so mirroring it is an idempotent replace, and the same transaction.updated that announces a duplicate message also marks it superseded; there is no window where it looks like real spend.
  • superseded (boolean) on each _embedded.messages[] item: the same fact, precomputed per message for renderers.

If you derive amounts from individual messages, exclude the superseded ones. Two clearings of 99.00 (one a duplicate feed delivery) plus a refund of −108.16 sum to 89.84 if you count everything, but the transaction's true transactionSettledAmount is −9.16, which is exactly what the active messages (99.00 − 108.16) sum to:

{
  "id": "884820d4-1b11-4dfd-ab83-988f754712da",
  "transactionSettledAmount": -9.16,
  "supersededMessageIds": ["934f8701-53e3-4a2b-a000-6bb52195d2ea"],
  "_links": {
    "messages": [
      { "href": "/transaction-messages/f4b24a0a-6a37-4ce3-8845-ddf0b604a9b4" },
      { "href": "/transaction-messages/934f8701-53e3-4a2b-a000-6bb52195d2ea" },
      { "href": "/transaction-messages/1c9e2a77-0d41-4b02-9a35-5f8fd21f406b" }
    ]
  }
}

For most integrations the simpler rule stands: use transactionSettledAmount / cardholderBillingSettledAmount directly. The aggregate's own totals already exclude superseded contributions.

Notes:

  • A superseded message is still a real, fetchable resource. Do not delete it from your records; treat it as non-financial context. Supersession is a fact about the message within this transaction's aggregation, so it is exposed on the transaction, not on the standalone message resource.
  • Transactions last updated before this field existed report supersededMessageIds: [] until their next update recomputes it.

Bank-sourced activity (bank linking)

When a bank account is linked, bank-observed money movement rides the SAME events documented above. There are no new event types or payload shapes to integrate. Three things are worth knowing:

1. Bank-only transactions. A bank movement with no card counterpart (payroll, transfers, fees, FX markup, and, on a card-less bank link, everything) arrives as a transactionmessage.created carrying a synthetic FINL_ADVC, followed by a transaction.created for the new SETTLED aggregate. The universal markers are: no approvalCode, a bank_-prefixed transactionReference, and a bankData block. A bank-only movement always carries network: "OPEN_BANKING". It never adopts a card network. On a card-less account it also carries cardId: null (_links.card omitted); on an account with linked cards it is attributed to the account's oldest linked card and carries that card's cardId (for feed grouping only, the network stays "OPEN_BANKING").

2. Earlier settlement. When a bank posting matches one of your card transactions with high confidence, the transaction settles early: a bank-sourced FINL_ADVC (inheriting the auth's transactionReference, approvalCode, and network) and a transaction.updated flipping PENDING→SETTLED, typically hours after the swipe instead of days. This applies per clearing leg: a purchase that clears in parts (for example 49 + 100 + 100 under one reference) can match each bank debit to its own leg, producing one transaction.match.created per debit and settling the card transaction leg by leg. When the card network's own clearing arrives later, the settled amounts may revise once more (tips, FX): the network remains the financial authority. Amount revision on transaction.updated is expected behavior, not an error. The provisional bank settlement is superseded rather than removed when that happens: its id joins supersededMessageIds and its embedded projection flips to superseded: true, so the bank provenance stays visible (see the Superseded messages section above).

3. Bank-first, card-later. If the bank movement arrived first as a standalone transaction and card data shows up later, the movement is re-settled onto the card transaction and the standalone's transaction.updated revises its settled amounts to 0 (status stays SETTLED). This netting fires for EVERY later match, whatever its confidence. A movement paired below the early-settlement bar still nets its standalone, so the same real-world spend is never represented twice. No new field or status to handle: consumers that already process amount revisions get this for free. Re-matching of late card data runs on a scheduled cadence (tight in the first hours, then progressively less frequent), and reopens automatically when newer card data arrives for the account. Matches can therefore appear well past the initial window. The card transaction's settlement and the standalone's netting revision ride independent stream partitions. They can arrive in either order. Key state by bankData.bankTransactionId and treat each event as an idempotent upsert, and both properties are harmless.

Linking the surfaces. bankData.bankTransactionId (on bank-sourced transactionmessage.created payloads and the _embedded.messages[] projection) equals banktransaction.created.id and transaction.match.created.matches[].id, and resolves via GET /bank-transactions/{subaccountId}/{id}: one join key across every bank-data surface, no extra REST calls needed to correlate push events.

A bank-sourced embedded message carries the join key in its bankData block (deliberately slim: the bank's merchant string, dates and amounts already map into the message's canonical fields; personalFinanceCategory is the one bank-only datum with no card-side equivalent):

{
  "bankData": {
    "provider": "plaid",
    "bankTransactionId": "7f2a9c4e-3b1d-4e8f-a5c7-6d0e2f9b8a3c",
    "personalFinanceCategory": {
      "primary": "FOOD_AND_DRINK",
      "detailed": "FOOD_AND_DRINK_RESTAURANT"
    }
  }
}

That bankTransactionId is the id of the corresponding banktransaction.created event (and matches[].id on transaction.match.created), so you can stitch the bank-feed events and this unified transaction into one record with no extra call.

Dedicated bank events. Beyond the transaction.* feed above, the banking service emits the six events below directly, and they can be subscribed to independently: the bank-linking lifecycle (banklink.completed, bankaccount.state_changed) and the bank-side records (banktransaction.created, banktransaction.deleted, transaction.match.created, transaction.match.deleted). They share the bank-transaction id join key described above.

banklink.completed

Sent when a bank-link enrollment finishes, via the manual PATCH /bank-links/{bankLinkId} completion or the hosted-link flow (Plaid SESSION_FINISHED plus the polling fallback). A completion emits exactly one banklink.completed, carrying the completed bankLinkId and all the bank accounts discovered under it in that single delivery (you never receive duplicate completion events for one link), so a push-only consumer can react to a new connection without polling GET /bank-links.

Each account row carries its mask (for credit accounts: the card number's last 4), normalized type, and the cards attached to it at completion: cardIds lists every linked card (including already-enrolled cards matched by last 4), and cards[] appears for credit accounts where completion created a bank-feed enrollment. Each entry pairs the new cardId with its subscription. A cardsubscription.created event (below) fires separately for every created card.

{
  "bankLinkId": "enrollment_amex_7f2c",
  "bankAccounts": [
    {
      "id": "059d5e6a-cfb1-46e6-ab84-6586582b6b58",
      "name": "Platinum Card",
      "mask": "1005",
      "type": "credit",
      "cardIds": ["9e107d9d-372b-4d1a-9f4a-654321abcdef"],
      "cards": [
        {
          "id": "9e107d9d-372b-4d1a-9f4a-654321abcdef",
          "subscriptionId": "1b671a64-40d5-491e-99b0-da01ff1f3341",
          "last4digits": "1005",
          "network": "amex",
          "enrollmentType": "bank-feed"
        }
      ]
    }
  ]
}

bankaccount.state_changed

Sent whenever a linked bank account's connection state actually changes: to auth_required (with a reason and, when known, an expiresAt), to disconnected, or back to active on recovery. Integrators can prompt the end user to re-authenticate (and stop prompting) without polling GET /bank-accounts. state is one of active, auth_required, or disconnected; reason and expiresAt are null unless applicable.

{
  "bankAccountId": "3a8f2c1d-5e7b-4d9a-b6c8-9f0e1d2a3b4c",
  "state": "auth_required",
  "previousState": "active",
  "reason": "consent_expiring",
  "expiresAt": "2026-07-09T00:00:00.000Z"
}

banktransaction.created

Sent when a bank transaction is synced from a linked account (via Plaid sync). Its id is the bank-transaction identifier that threads through every bank-data surface: it equals transaction.match.created.matches[].id and the bankData.bankTransactionId on the card feed, and resolves via GET /bank-transactions/{subaccountId}/{id}. amount is always positive. Direction is carried by transactionType (debit or credit). category is the primary personal-finance category (or null). The REST GET /bank-transactions resource exposes category as an array of provider categories instead; merchantName may be null.

{
  "id": "7f2a9c4e-3b1d-4e8f-a5c7-6d0e2f9b8a3c",
  "bankAccountId": "3a8f2c1d-5e7b-4d9a-b6c8-9f0e1d2a3b4c",
  "bankAccountName": "Checking",
  "date": "2025-10-15",
  "amount": 42.5,
  "currency": "USD",
  "description": "UBER TRIP",
  "merchantName": "Uber",
  "category": "Travel",
  "pending": false,
  "transactionType": "debit"
}

banktransaction.deleted

Sent when a previously announced bank transaction is removed: the provider retracted it (Plaid REMOVED), a pending duplicate was retired in favor of its posted twin, or a statement transaction was deleted. id is the same bank-transaction identifier the original banktransaction.created carried (also the event's metadata.resourceId), so a push-only consumer can drop the record without polling. Deletions from before this event existed were not announced. Reconcile older state via GET /bank-transactions.

{
  "id": "7f2a9c4e-3b1d-4e8f-a5c7-6d0e2f9b8a3c",
  "bankAccountId": "3a8f2c1d-5e7b-4d9a-b6c8-9f0e1d2a3b4c",
  "deletedAt": "2026-07-11T04:52:10.000Z",
  "reason": "provider_removed"
}

reason is one of provider_removed, duplicate_retired, statement_deleted. If the transaction had a match, a transaction.match.deleted follows with reason bank_transaction_retired.

transaction.match.created

Sent when the matching engine links a bank transaction to one of your card transactions. The payload includes the match's own matchId (address it at GET /transaction-matches/{subaccountId}/{matchId}; the REST resource exposes the same value as its top-level id), the confidence (HIGH ≥ 0.70, MEDIUM ≥ 0.50, LOW ≥ 0.20) and numeric score, the per-factor reasons behind the score, a summary of the matched card transaction, and the matched bank transaction(s) under matches[]. Each matches[].id is the bank-transaction id, the shared join key across the bank-data surfaces. A purchase that cleared in parts can produce one match event per clearing leg (each bank debit pairing with its own card message under the same reference). Expect multiple transaction.match.created events per purchase in that case, each with a distinct cardTransaction.id.

{
  "matchId": "b1c2d3e4-f5a6-7b8c-9d0e-1f2a3b4c5d6e",
  "confidence": "HIGH",
  "score": 0.88,
  "cardTransaction": {
    "id": "9c8b7a6d-5e4f-3c2b-1a0d-9e8f7c6b5a4d",
    "cardId": "5dec2c49-0aa5-4683-a317-427eb5d115f3",
    "date": "2025-10-15",
    "amount": 42.5,
    "currency": "USD",
    "descriptor": "UBER   *TRIP",
    "source": "Card"
  },
  "reasons": [
    {
      "type": "Amount",
      "score": 1,
      "cardValue": 42.5,
      "bankValue": 42.5,
      "diff": 0,
      "message": "Exact match"
    },
    {
      "type": "Date",
      "score": 1,
      "cardValue": "2025-10-15",
      "bankValue": "2025-10-15",
      "diff": 0,
      "message": "Exact match"
    },
    {
      "type": "Merchant",
      "score": 0.88,
      "cardValue": "UBER   *TRIP",
      "bankValue": "Uber",
      "diff": 12,
      "message": "88% similarity"
    }
  ],
  "matches": [
    {
      "id": "7f2a9c4e-3b1d-4e8f-a5c7-6d0e2f9b8a3c",
      "bankAccountId": "3a8f2c1d-5e7b-4d9a-b6c8-9f0e1d2a3b4c",
      "bankAccountName": "Checking",
      "date": "2025-10-15",
      "amount": 42.5,
      "currency": "USD",
      "source": "Plaid",
      "description": "UBER TRIP"
    }
  ]
}

transaction.match.deleted

Sent when a previously announced match is removed: its bank transaction was retired, a forced re-match superseded it with a different pairing, or a statement re-match rebuilt the statement's matches (recreated pairings get NEW match ids, so the old id genuinely dies and is announced). metadata.resourceId is the bank-transaction id, the same join key transaction.match.created carried. Matches removed before this event existed were not announced. Reconcile older state via GET /transaction-matches.

{
  "matchId": "b1c2d3e4-f5a6-7b8c-9d0e-1f2a3b4c5d6e",
  "bankTransactionId": "7f2a9c4e-3b1d-4e8f-a5c7-6d0e2f9b8a3c",
  "cardTransactionId": "9c8b7a6d-5e4f-3c2b-1a0d-9e8f7c6b5a4d",
  "reason": "rematch_superseded"
}

reason is one of bank_transaction_retired, rematch_superseded, statement_rematch. A deletion may be followed by a fresh transaction.match.created when a better pairing was persisted in the same pass.

cardsubscription.created

Payload consists of links to the created subscription.

For cardholder-single enrollments, networkBulkFeedId is null and the networkBulkFeed link is not present:

{
  "_links": {
    "card": {
      "href": "/cards/94b9b0ce-5775-470d-855e-0c0d0e72b0ea"
    },
    "self": {
      "href": "/card-subscriptions/aadf6c89-0356-4d94-8fa8-b5465077a0fb"
    }
  },
  "cardId": "94b9b0ce-5775-470d-855e-0c0d0e72b0ea",
  "createdAt": "2024-09-02T09:42:39.678Z",
  "customerReferenceId": null,
  "effectiveDate": "2024-09-02T09:42:39.677Z",
  "enrollmentType": "cardholder-single",
  "expirationDate": "2025-09-02T09:42:43.270Z",
  "id": "aadf6c89-0356-4d94-8fa8-b5465077a0fb",
  "networkBulkFeedId": null,
  "state": "reqSCA",
  "subaccountId": "6103874b-248d-4825-af88-a0d24dd9b123",
  "updatedAt": "2024-09-02T09:42:43.576Z"
}

For network-bulk enrollments, networkBulkFeedId and the networkBulkFeed link are included:

{
  "_links": {
    "card": {
      "href": "/cards/94b9b0ce-5775-470d-855e-0c0d0e72b0ea"
    },
    "networkBulkFeed": {
      "href": "/network-bulk-feeds/5ccc9922-e763-4492-b693-b6a922d96049"
    },
    "self": {
      "href": "/card-subscriptions/aadf6c89-0356-4d94-8fa8-b5465077a0fb"
    }
  },
  "cardId": "94b9b0ce-5775-470d-855e-0c0d0e72b0ea",
  "createdAt": "2024-09-02T09:42:39.678Z",
  "customerReferenceId": null,
  "effectiveDate": "2024-09-02T09:42:39.677Z",
  "enrollmentType": "network-bulk",
  "expirationDate": "2025-09-02T09:42:43.270Z",
  "id": "aadf6c89-0356-4d94-8fa8-b5465077a0fb",
  "networkBulkFeedId": "5ccc9922-e763-4492-b693-b6a922d96049",
  "state": "active",
  "subaccountId": "6103874b-248d-4825-af88-a0d24dd9b123",
  "updatedAt": "2024-09-02T09:42:43.576Z"
}

For bank-feed enrollments (a card created from a connected bank account at bank-link completion), bankAccountId back-references the source bank account and a bankAccount link is present. There is no separate event type for bank-derived cards. cardsubscription.created with enrollmentType: "bank-feed" IS the enrollment signal on this transport:

{
  "_links": {
    "card": {
      "href": "/cards/9e107d9d-372b-4d1a-9f4a-654321abcdef"
    },
    "bankAccount": {
      "href": "/bank-accounts/059d5e6a-cfb1-46e6-ab84-6586582b6b58"
    },
    "self": {
      "href": "/card-subscriptions/1b671a64-40d5-491e-99b0-da01ff1f3341"
    }
  },
  "cardId": "9e107d9d-372b-4d1a-9f4a-654321abcdef",
  "createdAt": "2026-07-14T12:00:00.000Z",
  "customerReferenceId": null,
  "effectiveDate": "2026-07-14T12:00:00.000Z",
  "enrollmentType": "bank-feed",
  "expirationDate": "2099-12-31T23:59:59.999Z",
  "id": "1b671a64-40d5-491e-99b0-da01ff1f3341",
  "networkBulkFeedId": null,
  "bankAccountId": "059d5e6a-cfb1-46e6-ab84-6586582b6b58",
  "state": "active",
  "subaccountId": "6103874b-248d-4825-af88-a0d24dd9b123",
  "updatedAt": "2026-07-14T12:00:00.000Z"
}

cardsubscription.updated

Payload consists of links to the updated subscription.

When it fires — the enrollment vocabulary. A subscription's identity is
established once by cardsubscription.created (the first source: a card
enrollment, a network-bulk feed, or a bank-feed mint). cardsubscription.updated
fires whenever that subscription changes afterwards — in particular when a
second source is layered onto it:

  • Card, then bank — a cardholder-single (or network-bulk) subscription
    whose card is later linked to a connected bank account gains bankAccountId
    (and a bankAccount link). enrollmentType is unchanged.
  • Bank, then card — a bank-feed subscription (created from a connected bank
    account) whose real card is later enrolled by PAN is upgraded in place: its
    enrollmentType transitions bank-feedcardholder-single, it keeps its
    bankAccountId, and the same id is retained. The card enrollment does not
    emit a second cardsubscription.created. The subscription also moves
    activereqSCA: the PAN you just added is a new card enrollment and,
    like any card enrollment, it has to clear SCA before the card is monitored.
    A later cardsubscription.updated reports reqSCAactive once it does.

A reqSCA subscription's bank link keeps working. state describes the
card side of a subscription — whether its PAN has cleared SCA — and does not
gate the bank side. While a subscription sits in reqSCA, its connected bank
account stays linked and its bank transactions keep syncing, matching, and
emitting banktransaction.created and transaction.match.created exactly as
before. So upgrading a bank-feed subscription by enrolling its PAN never costs
you bank-feed coverage while it waits for SCA: you gain the card side once SCA
clears, and lose nothing in the meantime — including if the cardholder abandons
SCA and the subscription stays in reqSCA.

Plus the usual lifecycle transitions (e.g. reqSCAactive after SCA,
activedeactivated).

For cardholder-single enrollments:

{
  "_links": {
    "card": {
      "href": "/cards/94b9b0ce-5775-470d-855e-0c0d0e72b0ea"
    },
    "self": {
      "href": "/card-subscriptions/aadf6c89-0356-4d94-8fa8-b5465077a0fb"
    }
  },
  "cardId": "94b9b0ce-5775-470d-855e-0c0d0e72b0ea",
  "createdAt": "2024-09-02T09:42:39.678Z",
  "customerReferenceId": null,
  "effectiveDate": "2024-09-02T09:42:39.677Z",
  "enrollmentType": "cardholder-single",
  "expirationDate": "2025-09-02T09:42:43.270Z",
  "id": "aadf6c89-0356-4d94-8fa8-b5465077a0fb",
  "networkBulkFeedId": null,
  "state": "active",
  "subaccountId": "6103874b-248d-4825-af88-a0d24dd9b123",
  "updatedAt": "2024-09-02T10:01:44.176Z"
}

When a cardholder-single subscription has been linked to a bank account
(card-then-bank), the payload additionally carries bankAccountId and a
bankAccount link — the same shape as the bank-feed payload below.

For network-bulk enrollments:

{
  "_links": {
    "card": {
      "href": "/cards/94b9b0ce-5775-470d-855e-0c0d0e72b0ea"
    },
    "networkBulkFeed": {
      "href": "/network-bulk-feeds/5ccc9922-e763-4492-b693-b6a922d96049"
    },
    "self": {
      "href": "/card-subscriptions/aadf6c89-0356-4d94-8fa8-b5465077a0fb"
    }
  },
  "cardId": "94b9b0ce-5775-470d-855e-0c0d0e72b0ea",
  "createdAt": "2024-09-02T09:42:39.678Z",
  "customerReferenceId": null,
  "effectiveDate": "2024-09-02T09:42:39.677Z",
  "enrollmentType": "network-bulk",
  "expirationDate": "2025-09-02T09:42:43.270Z",
  "id": "aadf6c89-0356-4d94-8fa8-b5465077a0fb",
  "networkBulkFeedId": "5ccc9922-e763-4492-b693-b6a922d96049",
  "state": "active",
  "subaccountId": "6103874b-248d-4825-af88-a0d24dd9b123",
  "updatedAt": "2024-09-02T10:01:44.176Z"
}

For bank-feed enrollments, the payload mirrors the bank-feed cardsubscription.created shape shown above: bankAccountId is populated and the bankAccount link is present. When the cardholder later enrolls that card's real PAN (bank-then-card), the subscription is upgraded in place and this event fires with enrollmentType transitioned to cardholder-singlebankAccountId and the bankAccount link are retained, and no second cardsubscription.created is emitted.

networkbulkfeed.statechanged


Webhook payload consists of a link to the related entity and the new and old states.

{
  "_links": {
    "self": {
      "href": "/network-bulk-feeds/884820d4-1b11-4dfd-ab83-988f754712da"
    }
  },
  "createdAt": "2024-05-01T06:41:43.810Z",
  "id": "884820d4-1b11-4dfd-ab83-988f754712da",
  "network": "MASTERCARD",
  "networkReference": "G234621",
  "newState": "active",
  "oldState": "submitted",
  "subaccountId": "c6d5e036-361e-4773-a514-b9496cf2d2b5",
  "updatedAt": "2024-05-02T08:41:43.810Z"
}

When a bulk feed transitions to the failed state, an errorReason field is included in the payload. This currently only applies to VISA bulk feeds.

{
  "_links": {
    "self": {
      "href": "/network-bulk-feeds/a23bb1e5-2bb9-4472-be8b-a500e21c8a42"
    }
  },
  "createdAt": "2026-04-08T22:54:32.111Z",
  "errorReason": "A bad request error occurred while requesting starter data: \"789452INVALID: Your Company ID is not a numeric value. Please provide a valid company ID!; No records found for the given inputs. Please provide valid inputs!\"",
  "id": "a23bb1e5-2bb9-4472-be8b-a500e21c8a42",
  "network": "VISA",
  "networkReference": "1_1_9999_789452INVALID",
  "newState": "failed",
  "oldState": "submitted",
  "subaccountId": "2f394ae7-5eef-4dfa-b3c3-0e973ae2ec51",
  "updatedAt": "2026-04-08T22:54:32.111Z"
}

When a bulk feed was created with expectedCardCount, the value is included on every webhook payload for that feed. Absent when the feed was created without it.

{
  "_links": {
    "self": {
      "href": "/network-bulk-feeds/884820d4-1b11-4dfd-ab83-988f754712da"
    }
  },
  "createdAt": "2024-05-01T06:41:43.810Z",
  "id": "884820d4-1b11-4dfd-ab83-988f754712da",
  "network": "MASTERCARD",
  "networkReference": "G234621",
  "newState": "active",
  "expectedCardCount": 5000,
  "oldState": "submitted",
  "subaccountId": "c6d5e036-361e-4773-a514-b9496cf2d2b5",
  "updatedAt": "2024-05-02T08:41:43.810Z"
}

enrichment.merchant.created

{
  "_links": {
    "message": {
      "href": "/transaction-messages/884820d4-1b11-4dfd-ab83-988f754712da"
    },
    "merchant": {
      "href": "https://www.amazon.com",
      "title": "Amazon"
    },
    "merchant-logo": {
      "href": "https://static.v2.spadeapi.com/logos/5f35110e8de74f9cadc6b28bf87dd5b6/light.png",
      "type": "image/png",
      "title": "Amazon logo"
    }
  },
  "id": "6fb8303a-3a91-41d1-a56d-5056cc7e84a9",
  "createdAt": "2025-10-20T17:38:317Z",
  "merchant": {
    "name": "Amazon",
    "category": "Online Marketplace"
  },
  "location": {
    "address": "1234 W 5th Ave Suite 100",
    "city": "New York",
    "state": "NY",
    "country": "USA",
    "postalCode": "10001",
    "phoneNumber": "+18664862360"
  }
}