Transaction Completed Event
Delivers a completed transaction to your callback URL.
Sent to every callback URL subscribed to the transaction event type when
an order of one of your financial institution's users fills: the trade
completed and the user's holdings and cash balance changed. Other
lifecycle movements (creation, pending states, cancellations, failures)
are not delivered.
A managed-portfolio (robo product) deposit, withdrawal, or rebalance
delivers one webhook for the whole transaction once it completes, not one
per buy/sell leg it placed. data.transaction_id names that
managed-portfolio transaction; the legs remain separate records in the
transaction feed.
data.status uses the same vocabulary the transaction endpoints report
and always carries completed today. Filter on data.status rather than
assuming every delivery is a completion, so new statuses can be delivered
in the future without breaking your consumer. The transaction is named by
the data.transaction_id the transaction endpoints resolve.
The payload deliberately carries no balances, positions, quantities, or
amounts. On receipt, refetch what you need: links.self points at the
authoritative transaction record, and the Get User Detail endpoint
returns the user's refreshed account values (for example to update a
balance shown in your app after the fill).
Each request carries two headers:
X-InvestiFi-Webhook-Id: the delivery's idempotency key, equal toevent_idand reused across retries. Deduplicate on it.X-InvestiFi-Webhook-Signature:t=<unix_seconds>,v1=<hex digest>. The digest is a hash-based message authentication code (HMAC-SHA256) over the timestamp, a period, and the raw request body, keyed by the signing secret returned from your first subscribe.v1is a scheme tag: match on it rather than assuming one value. The header is sent once your financial institution has a signing secret.
Verify against the raw request body, before parsing it, so the bytes match what was signed. Compare digests in constant time:
import hashlib
import hmac
parts = dict(p.split("=", 1) for p in signature_header.split(","))
signed = f"{parts['t']}.{raw_body.decode()}".encode()
expected = hmac.new(secret.encode(), signed, hashlib.sha256).hexdigest()
if not hmac.compare_digest(expected, parts["v1"]):
raise ValueError("Signature does not match")Delivery is at-least-once. Return any 2xx status to acknowledge; a non-2xx response or a timeout is retried.
- body
- The envelope POSTed to a subscribed callback URL.
Request Body
application/json
TypeScript Definitions
Use the request body type in TypeScript.
Response Body
Example Requests
The envelope delivered to a partner callback URL.
Every webhook shares this shape: data is a convenience so a partner
can act without a callback, and links.self keeps the API the source
of truth for consumers that would rather refetch.
/transactionSend Test Webhook
Requires the `manage:webhooks` scope. Send a sample event to the subscription's callback URL and report what it answered. The sample is signed and shaped exactly like a real delivery, so the response tells a partner whether their signature verification works before any real event depends on it. The payload describes a synthetic user and is flagged `is_test`, so it must never be processed as a real event. Delivery is one attempt with no retries.
Guides
End-to-end integration patterns built from the transaction and user endpoints.

