InvestiFiPartner APIBeta
Prod Environment

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

Parameters
subscription_id
Opaque id of the subscription to send the sample to.
Returns

The delivery outcome, carrying the callback URL's status code when it answered.

Raises
HTTPException
400 Bad Request when the callback URL no longer resolves to a public address, or 404 Not Found when the financial institution has no enabled subscription with that id.
https://partner-api.investifi.com/v1
POST
/webhooks/subscriptions/{subscription_id}/test

Authorization

APIKeyHeader
x-api-key<token>

Partner API key, required on every endpoint except "GET /health". Your financial institution issues and revokes these keys from the InvestiFi Ops Center. Each key carries an environment-identifying prefix such as "ifi_prod_" plus a fixed set of scopes, so a valid key still returns 403 Forbidden on an endpoint whose scope it was not granted; the response "detail" names the check that failed.

In: header

Path Parameters

subscription_id*Subscription Id

Response Body

application/json

application/json

application/json

curl -X POST "https://partner-api.investifi.com/v1/webhooks/subscriptions/string/test"
{
  "delivered": true,
  "response_status_code": 0,
  "event_id": "string"
}
{
  "detail": "Key does not have the required scope: manage:webhooks."
}
{
  "detail": [
    {
      "loc": [
        "string"
      ],
      "msg": "string",
      "type": "string",
      "input": null,
      "ctx": {}
    }
  ]
}

KYC Status Change Event

Delivers a user's identity verification status change to your callback URL. Sent to every callback URL subscribed to the `kyc` event type when a user's status changes for one of your financial institution's investing products. Intermediate states carrying no outcome you can act on are not delivered. One subscription covers every delivered status: filter on `data.kyc_status` (`pending`, `document_requested`, `approved`, `rejected`, `closed`, `account_takeover`) rather than subscribing per status. A user who has finished onboarding into a product reports `approved` for that product, named in `data.product`. 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: ```python 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. `links.self` points at the authoritative user record for consumers that would rather refetch than trust the payload. Each subscription is pinned to the API version it was created through, and `links.self` is built at that version: a subscription created via the `v1` subscribe endpoint emits `/v1/...` links, and keeps doing so after newer API versions ship. When you migrate to a new API version, switch both at the same time: point your API calls at the new base URL and re-create your webhook subscriptions through the new version's subscribe endpoint, then delete the old subscriptions.

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: ```python 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.