Skip to content

Events


Asynchronous interactions between InvestSuite and the Client Middleware can happen in multiple ways, depending on the direction. The below table summarizes the capabilities:

Inbound InvestSuite Outbound InvestSuite
Event Driven Not supported The Client Middleware responds to an event that is sent by the InvestSuite system
REST /events/ Endpoint The Client Middleware notifies InvestSuite of an event through the POST /events/ endpoint Under development The Client Middleware regularly polls the GET /events/ endpoint

Event Driven

Outbound

Architectural concept

  • InvestSuite posts events on one single topic of the client's event queue
  • Any AMQP/MQTT compatible message bus is supported, eg. Kafka, RabbitMQ, Azure Service Bus...

Principles

  1. The strategy is to signal that an event has happened, not to include all information that the middleware may possibly require (as these requirements may vary wildly and may introduce breaking changes). Therefore, an additional REST API call may be required to get additional data.

  2. All events will use this envelope, with the actual information in data.

  3. The fully qualified name is a combination of the subject and the action mentioned in the event.

{
  "id": string, 
  "created": timestamp, 
  "version": string,
  "subject": string,
  "action": string, 
  "data": dict
}
Field Type Content
id string An ID uniquely identifying the event
created timestamp The timestamp of the creation of the event
version string The version of the event object. For now, this will always be "1.0.0"
subject string A string representing a subject, always referring to an entity (see Entities Overview)
action string The action on the topic entity that triggers the event, for example 'creation'
data dict A dictionary that contains specific fields, depending on the type, see below for the exhaustive list.

Note

We are deliberately using ‘subject’ i.o. ‘topic’, since

  • the integration pattern with 3rd parties is that we will post all messages on 1 topic

  • the subject field can, potentially in the future, be broader than only entities (see above)

Portfolios

Creation

Info

Applies to: Robo Advisor, Self Investor

Fully qualified name: portfolios.creation

When IVS supplies the the front-end application to the b2b client they should be notified by IVS when a (real money) portfolio is created. That way, the client can store the IVS portfolio_id at their side and (optionally) link it to the user in their database.

It is up to the b2b client to check if the portfolio is “paper_money” or “real_money” based on this event. This to decide if they need to proceed with setup at their side.

{
  "id" : "an-event-id",
  "created" : "-1000000000-01-01T00:00:00Z",
  "version" : "1.0.0",
  "subject" : "portfolios",
  "action" : "creation",
  "data" : {
    "id" : "a-portfolio-id",
    "external_id" : "an-external-id",
    "owned_by_user_id" : "a-user-id",
    "owned_by_external_user_id": "portfolio-owner-external-user-id"
}
Status update

Info

Applies to: Robo Advisor, Self Investor

Fully qualified name: portfolios.status-update

Every time a status of a portfolio gets updated an event will be sent to the b2b client.

Possible values for the status field:

  • WAITING_FOR_ACCOUNT: brokerage accounts are to be added by the b2b client
  • WAITING_FOR_POLICY: user is still going through the risk profiling, so a policy was not yet determined.
  • WAITING_FOR_FUNDS: waiting for incoming funds to arrive. This can be a trigger for the b2b client to push the client to make a transfer if this status isn’t updated within a certain timeframe.
  • ACTIVE: portfolio is in use
  • INACTIVE
{
  "id" : "an-event-id",
  "created" : "-1000000000-01-01T00:00:00Z",
  "version" : "1.0.0",
  "subject" : "portfolios",
  "action" : "status-update",
  "data" : {
    "id" : "a-portfolio-id",
    "external_id" : "an-external-id",
    "value" : "INACTIVE",
    "owned_by_user_id" : "a-user-id",
    "owned_by_external_user_id": "portfolio-owner-external-user-id"
  }
}

Possible values for the value field:

  • WAITING_FOR_POLICY
  • WAITING_FOR_ACCOUNT
  • WAITING_FOR_FUNDS
  • ACTIVE
  • INACTIVE
Withdrawal request

Info

Applies to: Robo Advisor

Fully qualified name: portfolios.withdrawal

Whenever a user wants to withdraw money, IVS will update the divest amount on the portfolio object.

Every update to this amount will trigger an event towards the b2b client.

Divest amount:

  • 1e100 → full withdrawal.
  • a specific amount → partial withdrawal

Note: value is the total amount that is requested for withdrawal, not the incremental value (eg. in case of two quickly subsequent withdrawal requests, where the first is not yet settled)

{
  "id" : "an-event-id",
  "created" : "-1000000000-01-01T00:00:00Z",
  "version" : "1.0.0",
  "subject" : "portfolios",
  "action" : "divest-amount-update",
  "data" : {
    "id" : "a-portfolio-id",
    "external_id" : "an-external-id",
    "divest_amount" : 1.234,
    "currency" : "a-currency-code",
    "owned_by_user_id" : "a-user-id",
    "owned_by_external_user_id": "portfolio-owner-external-user-id"
  }
}
Cash available for withdrawal

Info

Applies to: Robo Advisor and only if there is no broker integration

Fully qualified name: portfolios.cash-available-for-withdrawal

Whenever a withdrawal is requested (divest_amount is set) or the holdings of a real_money portfolio are updated, we check if there is sufficient cash available to execute the requested withdrawal. If this is the case, an event is sent to notify you that a withdrawal is possible, as InvestSuite will not execute any payment transactions towards the benefiary account.

The actual divest_amount can be fetched by calling the portfolio endpoint.

Divest amount:

  • 1e100 → full withdrawal.
  • a specific amount → partial withdrawal

Note: value is the total amount that is requested for withdrawal, not the incremental value (eg. in case of two quickly subsequent withdrawal requests, where the first is not yet settled)

{
  "id" : "an-event-id",
  "created" : "-1000000000-01-01T00:00:00Z",
  "version" : "1.0.0",
  "subject" : "portfolios",
  "action" : "cash-available-for-withdrawal",
  "data" : {
    "id" : "a-portfolio-id",
    "external_id" : "an-external-id",
    "currency" : "a-currency-code",
    "owned_by_user_id" : "a-user-id",
    "owned_by_external_user_id": "portfolio-owner-external-user-id"
  }
}
Suitability profile created for a portfolio

Info

Only when Suitability profiler is used

Fully qualified name: portfolios.profile-created-for-portfolio

{
  "id" : "an-event-id",
  "created" : "-1000000000-01-01T00:00:00Z",
  "version" : "1.0.0",
  "subject" : "portfolios",
  "action" : "profile-created-for-portfolio",
  "data" : {
    "id" : "a-portfolio-id",
    "external_id" : "an-external-id",
    "owned_by_user_id" : "a-user-id",
    "suitability_profile_id": "profile-id-linked-to-portfolio",
    "owned_by_user_id": "a-user-id",
    "owned_by_external_user_id": "external-user-id"
  }
}
Suitability profile updated for a portfolio

Info

Only when Suitability profiler is used

Fully qualified name: portfolios.profile-updated-for-portfolio

{
  "id" : "an-event-id",
  "created" : "-1000000000-01-01T00:00:00Z",
  "version" : "1.0.0",
  "subject" : "portfolios",
  "action" : "profile-updated-for-portfolio",
  "data" : {
    "id" : "a-portfolio-id",
    "external_id" : "an-external-id",
    "owned_by_user_id" : "a-user-id",
    "suitability_profile_id" : "profile-id-linked-to-portfolio",
    "owned_by_user_id" : "a-user-id",
    "owned_by_external_user_id" : "external-user-id"
  }
}

Optimizations

Status update

Info

Applies to: Robo Advisor

Fully qualified name: optimizations.status-update

When an optimization is ready we will notify the b2b client when the optimization was successfully created.

Fields:

  • is_recommended: only when this is “True” the optimization will be executed (discretionary) or will be proposed to the client for acceptance (advisory).

  • Possible values for the status field: PENDING, SUCCESS, FAILURE, INFEASIBLE, NOT_OPTIMIZED

Info

In v1.0.0 (aug 2022) only status “SUCCESS” is used.

{
  "id" : "an-event-id",
  "created" : "-1000000000-01-01T00:00:00Z",
  "version" : "1.0.0",
  "subject" : "optimizations",
  "action" : "status-update",
  "data" : {
    "id" : "an-optimization-id",
    "portfolio_id" : "a-portfolio-id",
    "external_id" : "an-external-id",
    "is_recommended" : true,
    "value" : "SUCCESS"
  }
Owner choice update

Fully qualified name: optimizations.owner-choice-update

This event is specifically useful for b2b clients who implemented an advisory flow (advisory mandate).

In this case an optimization may only be executed when the owner choice has been updated to “ACCEPT”

The id can be used to fetch the optimization.

  • Possible values for the status field are ACCEPT, REJECT, REOPTIMIZE, IGNORED
{
  "id" : "an-event-id",
  "created" : "-1000000000-01-01T00:00:00Z",
  "version" : "1.0.0",
  "subject" : "optimizations",
  "action" : "owner-choice-update",
  "data" : {
    "id" : "an-optimization-id",
    "portfolio_id" : "a-portfolio-id",
    "external_id" : "an-external-id",
    "value" : "ACCEPT"
  }
}

Profiles

Assessment status update

Fully qualified name: profiles.assessment-status-update

Events are sent when a status of an assessment within a profile change. Assessments can return different states:

  • IN_PROGRESS: the assessment is incomplete, either just created or started.
  • COMPLETED: the assessment is completed but the result has not been accepted.
  • ACCEPTED: final state, the assessment has been completed and the result has been accepted by the user.
  • COOLDOWN: An assessment has been tried multiple times and is in a state where a user has to wait until they can retry the assessment.
  • EXPIRED: Whenever an assessment has been ACCEPTED it can expire after a given period. This means the user will need to reassess.

To get the linked portfolio the query language can be used to fetch the portfolio which is linked to the mentioned by performing following call: GET /portfolios/?query=config.manager_settings.suitability_profile_id+eq+'profile_id'

The state of a profile can be fetched based on the given 'profile_id': GET /suitability-profiler/profiles/{profile_id}/

For more information see our Integration openAPI documentation.

{
  "id" : "an-event-id",
  "created" : "-1000000000-01-01T00:00:00Z",
  "version" : "1.0.0",
  "subject" : "assessments",
  "action" : "assessment-status-update",
  "data": {
    "profile_id": "N1234...", 
    "questionnaire_id": {internal id}
    "value": "IN_PROGRESS/COMPLETED/ACCEPTED/EXPIRED"
    "accepted_datetime": timestamp (included only when status is ACCEPTED)}
}

REST /events/ endpoint

Inbound

Funding (deposit) event

POST /events/deposit/ HTTP/1.1
Host: api.sandbox.investsuite.com
Content-Type: application/json
Idempotency-Key: "LVRYWG833Vp2FIIG"

{
    "data": {
        "amount": "1000",
        "currency": "USD",
        "portfolio": "P01F8ZSNV0J45R9DFZ3D7D8C26F",
        "external_id": "example_external_id"
    }
}

Warning

In case of broker integration by InvestSuite, this event triggers the Funding flow. Therefore we strongly suggest using the idempotency-key to avoid double deposits if the middleware replays the event.

We recommend using the id of the transaction generated by the core banking system.

In case the broker is Saxo please limit the idempotency-key to max 10 characters.

Withdrawal executed event

Info

Not to be confused with the withdrawal requested event.

POST /events/withdraw/ HTTP/1.1
Host: api.sandbox.investsuite.com
Content-Type: application/json
Idempotency-Key: "LVRYWG833Vp2FIIG"

{
    "data": {
        "amount": "1000",
        "currency": "USD",
        "portfolio": "P01F8ZSNV0J45R9DFZ3D7D8C26F"
    }
}

Warning

We strongly suggest using the idempotency-key to avoid double deposits.

Outbound

Warning

This is still under development