Skip to main content

Overview

Event listeners are used to observe and intercept actions taken inside the Embeddable UI. You might, for example, react to a user linking a bank account, or take over the library’s built-in behavior entirely. Listeners are shared across all instances of the Embeddable UI. Registrations across different components share the same state, and the last registration for a given key wins, so prefer a single registration site per event key to avoid conflicts. The available events are listed below under Reference.

Use Cases

Event listeners open up a wide range of possibilities for tailoring the Embeddable UI to your platform. The examples below illustrate some common patterns.

Invoice Routing

If your platform has its own invoicing, event listeners let you route users back to your invoices from within the Embeddable UI. For example, a vertical SaaS platform issues invoices through its own product, and the Tight Transactions Dashboard shows a payout that includes three of those invoices. When the user clicks on one of those invoices, you can intercept the click and route the user to the corresponding invoice in your platform instead.

Paywall Enforcement

Event listeners also let you gate specific functionality behind your own pricing tiers. For example, a horizontal SaaS platform wants to offer Tax Estimates only on a paid plan. When a user clicks into tax-related functionality, the platform intercepts the routing and shows its own paywall screen instead of the built-in tax experience.

Setting Listeners

Listeners are accessed via the useEmbeddedEventListeners hook. Keep in mind that this must be used within a <Tight> component. Listener declaration is done either by passing initial listeners to the hook, or by using the setListeners function returned by it. This function accepts a subset of all possible listeners, and will merge any input into your already defined listeners. This means that both this:
import { useEmbeddedEventListeners } from "@tight-embedded/react";
// ...
const [listeners, setListeners] = useEmbeddedEventListeners({
  plaidLink: {
    listener: (event) => {
      if (event.result === "SUCCESS") {
        console.log("Bank linked:", event.type);
      }
    },
  },
});
and this:
import { useEmbeddedEventListeners } from "@tight-embedded/react";
// ...
const [listeners, setListeners] = useEmbeddedEventListeners();
setListeners({
  plaidLink: {
    listener: (event) => {
      if (event.result === "SUCCESS") {
        console.log("Bank linked:", event.type);
      }
    },
  },
});
are valid methods of defining the same set of listeners.
The initial listeners passed to useEmbeddedEventListeners are only applied once, on mount. Re-renders do not re-apply them. To update a listener after mount, use setListeners.

Updating Listeners

Since setListeners accepts a subset of listeners, you’re able to update your listeners at any time after mount. The code below adds a gusto listener without affecting any previously registered keys.
setListeners({
  gusto: { listener: handleGusto, preventDefault: true },
});
Note that listener merging is a shallow merge - input is only merged at the top level.

Removing Listeners

If you want to remove a listener, pass null for its key. Unrelated keys are preserved.
setListeners({ gusto: null });

Preventing Default Behavior

Each listener is registered as an EventRegistration, which pairs your callback with an optional preventDefault flag. When preventDefault is true, the library’s built-in behavior for that event is suppressed (for example, the Plaid modal will not open; the transaction drawer will not open). When false or omitted, both your listener and the default behavior run. For example, to open a bank’s website in a custom in-app browser instead of a new tab, suppress the default behavior and handle the navigation yourself:
setListeners({
  plaidLink: {
    preventDefault: true,
    listener: (event) => {
      if (event.type === "PLAID_LINK_ROUTE_TO_BANK" && event.result === "SUCCESS") {
        openInAppBrowser(event.data.bankUrl);
      }
    },
  },
});

Handling Events

Every event is a discriminated union with a result field:
type TightEvent<T, D, E> =
  | { type: T; result: "SUCCESS"; data: D;    error: null }
  | { type: T; result: "FAILURE"; data: null; error: E    }
Always branch on event.result before reading data or error.
listener: (event) => {
  if (event.result === "SUCCESS") {
    // event.data is typed
  } else {
    // event.error is typed
  }
}

Reference

useEmbeddedEventListeners returns a [listeners, setListeners] tuple:
listeners
object
A snapshot of all currently registered listeners.
setListeners
function
A function to merge new registrations into the existing state. Each key accepts an EventRegistration, or null to remove that listener.

Events

Each row below is a registration key you can pass to useEmbeddedEventListeners. The Event types column lists the event.type values a single registration may receive, and the data and error shapes are the payloads on SUCCESS and FAILURE results respectively.
Registration keyDescriptionEvent types, data, and error shapes
plaidLinkFires at the start of each bank-linking action; a single registration handles all lifecycle types. preventDefault: true prevents the Plaid modal from opening, letting you manage the flow yourself.Types: PLAID_LINK_NEW, PLAID_LINK_RESET, PLAID_LINK_RELINK, PLAID_LINK_UNLINK, PLAID_LINK_ROUTE_TO_BANK. Data on SUCCESS: { plaidAccessToken: string } | null (reset/relink/unlink), { bankUrl: string } (route to bank), or null (new). Error on FAILURE: { errorCode: string }.
plaidLinkEventFires for every event emitted by the Plaid SDK during the link flow. Useful for analytics.Type: PLAID_LINK_EVENT. Data on SUCCESS: { eventName: string; metadata: unknown }.
plaidLinkErrorFires when the Plaid flow encounters an error or the user exits with an error.Type: PLAID_LINK_FAILURE. Error on FAILURE: { displayMessage: string; errorCode: string; errorMessage: string }.
gustoFires when the user taps “Connect Gusto” in the Banks & Integrations section. preventDefault: true suppresses the built-in Gusto OAuth flow so you can initiate OAuth from your own backend.Type: GUSTO_NEW. Data on SUCCESS: null.
conversationsFires when the user opens or starts a conversation. preventDefault: true suppresses the library’s conversation drawer.Type: OPEN_CONVERSATION. Data on SUCCESS: ConversationGetDto (existing conversation), { defaultValues: MessageFormValues } (new conversation with pre-filled text), or null (blank new conversation).
transactionFormFires when a transaction form opens or closes; delineates between transactions from processor (ie. Stripe, Square, etc.) and non-processor sources. preventDefault: true on open prevents the library’s transaction drawer from opening, letting you render your own UI.Types: OPEN_BANK_TRANSACTION_FORM, CLOSE_BANK_TRANSACTION_FORM, OPEN_PROCESSOR_TRANSACTION_FORM, CLOSE_PROCESSOR_TRANSACTION_FORM. Data on SUCCESS: BankTransaction (editing), null (creating), or BankTransaction | null (close). Error on FAILURE: Record<string, unknown>.
bITileFinancial dashboard tiles. Fires when a dashboard tile mounts. Useful for tracking which tiles are visible. preventDefault has no effect on tile events.Types: CASH_FLOW_TILE, EXPENSE_TILE, PROFIT_AND_LOSS_TILE. Result: SUCCESS when the corresponding tile mounts.
billBill drawer. Fires when the user opens a bill.Type: OPEN_BILL. Data on SUCCESS: BillGetDto. Error on FAILURE: Record<string, unknown>.
customerCustomer selector/form. Fires when the user opens the customer selector or form.Type: OPEN_CUSTOMER. Data on SUCCESS: CustomerGetDto (existing customer) or null (new customer).
payrollPayroll record. Fires when the user opens a payroll record.Type: OPEN_PAYROLL. Data on SUCCESS: { data: PayrollGetDto }.
taxDashboardZeroStateCTAFires when the user clicks the zero-state call to action on the Tax Dashboard.Type: TAX_DASH_CTA. Data on SUCCESS: null.
taxProfileCTAFires when the user clicks the tax profile call to action on the Tax Dashboard or Financial Overview.Type: TAX_PROFILE_CTA. Data on SUCCESS: null.
taxFilingCTAFires when the user clicks a tax filing call to action on the Tax Dashboard.Type: TAX_FILING_CTA. Data on SUCCESS: { action: "CONTINUE" | "VIEW" | null }.
learnMoreFires when the user clicks a “Learn more” link within the import flows.Types: IMPORT_CSV_LEARN_MORE, IMPORT_RULES_LEARN_MORE, SCRIPT_BLOCKER_LEARN_MORE, IMPORT_XERO_LEARN_MORE. Data on SUCCESS: null.
paymentProcessorTutorialFires when the user opens the payment processor tutorial from the Invoice Dashboard or Financial Overview.Type: PAYMENT_PROCESSOR_TUTORIAL. Data on SUCCESS: null.
paymentProcessingFires for payment processing actions, such as refunding an invoice from the Invoice Dashboard or setting up payment processing. Also registered under the invoice alias.Types: REFUND_INVOICE, SETUP_PAYMENT_PROCESSING. Data on SUCCESS: { invoiceId: string } (refund) or null (setup).
renderClientInvoiceFires when a client pays an invoice from the client-facing invoice view.Type: PAY_INVOICE. Data on SUCCESS: { invoiceId: string; invoiceName: string; invoiceBalance: number }.
incomeDashCTAFires when the user clicks the send invoice call to action on the Income Dashboard.Type: SEND_INVOICE. Data on SUCCESS: null.
accountantUserActionFires when an accountant takes an action on a user.Type: ACCOUNTANT_USER_ACTION. Data on SUCCESS: { value: string; userId: string }.
accountantFires when members or teammates are added, updated, removed, or restored.Types: ADD_MEMBERS, UPDATE_MEMBERS, REMOVE_MEMBERS, RESTORE_MEMBERS, ADD_TEAMMATE, UPDATE_TEAMMATE, REMOVE_TEAMMATE, RESTORE_TEAMMATE. Data on SUCCESS: { users: Array<{ id: string; userId: string; email?: string }> } (member types), { teammate: object } (add/update teammate), or { id: string; userId: string } (remove/restore teammate).
importFires when an import flow completes.Types: IMPORT_EXPENSE_RULES_COMPLETED, IMPORT_QBO_COMPLETED, IMPORT_XERO_COMPLETED, IMPORT_BENCH_COMPLETED, IMPORT_WAVE_COMPLETED. Data on SUCCESS: null.

TypeScript

All types are exported from @tight-embedded/react:
import type {
  TightEvent,
  TightEventListener,
  TightEvents,
  EventRegistration,
  PlaidLinkEvent,
  UseEmbeddedEventListenersResult,
} from "@tight-embedded/react";