Integration Guide

Prerequisites

Before you start, make sure you have both your API credentials (for server-to-server calls) and, if you're enrolling cards client-side, your SDK credentials — see Authentication.

Every step below assumes you already have a webhook registered for the relevant event types — none of these events reach you until you do. Register one with POST /webhooks (url, eventTypes, subaccountId); the response's secret is returned once, at creation, and is what you use for signature verification. See Webhooks for registration, retry/redelivery semantics, and the (required) signature verification step.

Business Actions → Technical Implementation

Business EventSuggested WebhookSuggested API CallAssociated ResourcesNotes
Corporate is onboardedPOST /subaccountsSubaccountEnrollment controls and verification policy can be set at creation, or later via PATCH.
Bank account is connectedbanklink.completedPOST /bank-linksBank Link, Bank AccountOptional — cardholder completes a hosted Plaid flow; see Step 2. Independent of card enrollment; connecting early means every card enrolled afterward gets earlier settlement from its first transaction, with no manual backfill needed.
Card is enrolledcardsubscription.createdCard SubscriptionSee Quick Start: Card Enrollment.
Card is linked to a bank account(no dedicated event)PATCH /bank-accounts/{bankAccountId} (manual override only)Bank AccountDetected automatically on the card's first high-confidence match against a connected account's feed. transaction.match.created for that cardId is a signal it's happened, but the authoritative check is GET /bank-accounts/{bankAccountId}cardIds. The PATCH call is only needed to pre-link, correct, or backfill.
Expense is madetransaction.createdTransaction, Transaction MessageAlso fires for bank-only activity once a bank account is connected — see Step 2.
Expense record is updatedtransaction.updatedTransactionAmount revisions (tips, FX, early settlement) are expected behavior, not an error.
Card is deactivatedPATCH /card-subscriptions/{subscriptionId}Card SubscriptionFinal — cannot be undone, and not available for bulk-enrolled subscriptions.
Card is re-enrolledcardsubscription.createdCard Subscription

Steps

Step 1: Corporate Is Onboarded

Create a Subaccount for the corporate customer — it's the unit Cards, Transactions, and now Bank Accounts are segmented under, and where entitlements (enrollment controls, verification policy) apply.

POST /subaccounts HTTP/1.1
Content-Type: application/json

{
  "name": "Acme Corp",
  "configurations": {
    "VISA": { "countries": "*" },
    "MASTERCARD": { "countries": "*" }
  }
}
curl -X POST https://api.astrada.co/subaccounts \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Acme Corp",
    "configurations": {
      "VISA": { "countries": "*" },
      "MASTERCARD": { "countries": "*" }
    }
  }'
{
  "name": "Acme Corp",
  "configurations": {
    "VISA": { "countries": "*" },
    "MASTERCARD": { "countries": "*" }
  }
}

configurations is required — "*" is the safer default unless you specifically need a narrower set. See Account & Subaccount Configuration for the full validation rules and how this relates to your account's own capabilities.

Step 2: A Bank Account Is Connected

This step is optional. Card enrollment and everything after it (Steps 3–7) works completely independently — skip to Step 3 if you have no need for bank data.

If you do want it, connecting a bank account unifies a cardholder's card and bank activity onto the same Transaction feed used for card spend, and unlocks earlier settlement for any card enrolled afterward. It has no dependency on card enrollment — doing it here, before any card exists, is what lets every card enrolled from Step 3 onward benefit automatically from its very first transaction.

POST /bank-links HTTP/1.1
Content-Type: application/json

{ "id": "enrollment_792dcb7d", "subaccountId": "c6d5e036-361e-4773-a514-b9496cf2d2b5" }
curl -X POST https://api.astrada.co/bank-links \
  -H "Content-Type: application/json" \
  -d '{ "id": "enrollment_792dcb7d", "subaccountId": "c6d5e036-361e-4773-a514-b9496cf2d2b5" }'
{ "id": "enrollment_792dcb7d", "subaccountId": "c6d5e036-361e-4773-a514-b9496cf2d2b5" }

The response includes a hostedLink (a Plaid-hosted URL, 4-hour TTL) to distribute to the cardholder. Once they finish connecting, banklink.completed fires with the discovered Bank Accounts:

{
  "bankLinkId": "enrollment_792dcb7d",
  "bankAccounts": [{ "id": "3a8f2c1d-5e7b-4d9a-b6c8-9f0e1d2a3b4c", "name": "Checking" }]
}

From here, bank-only activity (fees, transfers, payroll — anything with no card counterpart) starts arriving as ordinary transaction.created events.

Full payload detail for all bank-sourced activity, including the dedicated bankaccount.state_changed, banktransaction.created, and transaction.match.created events, is in the "Bank-sourced activity" section of Event Types.

Step 3: Card Is Enrolled

Enroll a card under the Subaccount — either via the Card Enrollment SDK or directly against the API. On success, cardsubscription.created fires. See Quick Start: Card Enrollment.

If a bank account is already connected (Step 2), this card enables earlier settlement automatically: the first time its transactions produce a high-confidence match against the account's feed, Astrada links them — no API call required. PATCH /bank-accounts/{bankAccountId} is the manual override, for pre-linking a card before any spend exists, correcting an assignment, or importing history via backfill (auto-detected links don't backfill).

Step 4: An Expense Is Made

Card spend produces one or more Transaction Messages (transactionmessage.created fires for each) that amalgamate into a Transaction, delivered via transaction.created. The payload carries links to the Transaction and its related Transaction Message(s):

{
  "_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" }]
  },
  "id": "884820d4-1b11-4dfd-ab83-988f754712da",
  "status": "SETTLED",
  "transactionType": "DEBIT_01"
}
📘

Want the message detail inline?

transaction.created and transaction.updated can carry an _embedded.messages projection of the
correlated Transaction Messages, so you don't need a follow-up call. To opt in, reach out to your
Astrada Account Manager or [email protected] — full detail in
Event Types.

Step 5: An Expense Record Is Updated

Later message activity against the same Transaction (settlement, reversal, a bank-sourced match) fires transaction.updated with the same shape, reflecting the current state.

Step 6: Card Is Deactivated

PATCH /card-subscriptions/{subscriptionId} HTTP/1.1
Content-Type: application/json

{ "state": "deactivated" }
curl -X PATCH https://api.astrada.co/card-subscriptions/{subscriptionId} \
  -H "Content-Type: application/json" \
  -d '{ "state": "deactivated" }'
{ "state": "deactivated" }

This is final and cannot be undone, and isn't available for subscriptions enrolled in bulk (enrollmentType: "network-bulk").

Step 7: Card Is Re-Enrolled

Re-enrolling follows the same path as Step 3 — cardsubscription.created fires again. The same underlying Card is reused (same cardId); re-enrollment creates a new Card Subscription (a new subscriptionId) for it.

Available Webhooks

Beyond the events in the flow above, Astrada also emits cardsubscription.updated, networkbulkfeed.statechanged, and (experimental) enrichment.merchant.created. Full trigger conditions and sample payloads for every event: Event Types.