InvestiFiPartner APIBeta
Prod Environment

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 to event_id and 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. v1 is 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.

Parameters
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.

POST/transaction