KYC Flow Integration Guide

KYC Flow Integration Guide

This page is under heavy construction, latest update 23.06.2026 by @Benjamin Harbakk . New examples are being added continuously.

 

KYC Flow is a management system for Knowing Your Customer. It offers out-of-the-box monitoring for PEP and Sanctions.

A typical use of KYC looks like this:

  1. Create a customer

    1. A lookup starts automatically

    2. An AML report is automatically generated.

    3. They are added to Monitor automatically.

  2. Send them a webform asking for more information.

    1. Webforms are customizable and can be tailored to spesific individual customers, or general templates can be made to target a wide demographic of customers.

  3. Perform a Risk assessment

    1. As new information arises (PEP alerts, sanctions, company role changes, company address changes, beneficial owner changes), perform new risk assessments.

 

A typical integration that communicates with the KYC API typicallly involes a few steps:

  • Creation of the assignment in KYC Flow (including base info like: what participants are included)

  • What events do you care about in your external system?

    • KYC Flow publishes a ton of webhooks whenever something noteworthy happens.

    • You can subscribe to “ALL” or just the subset you care about.

  • Do you want KYC Flow to handle the sending of Emails for Webforms, or do you want to handle it yourself?

    • KYC Flow has a reminder system for sending out emails if the webform has not been filled out

    • These can instead be owned by you through webhooks.

  • KYC Flow allows you to subscribe to webhooks for events that occur internally.

Useful Links

KYC Flow exposes an an OpenAPI specification on all running instances of KYC Flow. Each customer who wants a test environment can typically access the docs throughhttps://{yourtestdomain}.ksp-stoedev.no/swagger/index.html

This is recommended, because you can log into this environment and use the “Try Me!” functionality built into swagger docs using your logged-in user.

NOTE: Be sure to select “V2” in the dropdown in the upper right to get up-to-date

If you do not currently have a provisioned test environment, you can check the docs through here, but wont be able to test.

  • https://staging.ksp-stoedev.cloud/swagger/index.html

  • Redoc UI We also have a REDOC version, but it loads more slowly on first-time-load (1 minute or longer) because of a bug in Redoc’s parsing causing a recursion. We are looking at options.

Glossary

Physical and Legal Persons

KYC Flow has adopted the distinction of “Physical Person” and “Legal Person”.

  • PhysicalPersons are always human beings.

    • They typically have National Identity Numbers + Country, or FirstName, LastName, Date of Birth and Country as the way to distinguish between unique persons.

    • Their primary identifier in KYC Flow is the PhysicalPersonId

  • LegalPersons are legal entities that are sometimes also human beings (in the case of Sole proprietorships).

    • They typically have Organization numbers as their primary identifier across systems.

      • A physical person who is also a Sole Proprietorship can also be a Legal Person, and in some countries (Sweden) they even use the National ID as their Organisation numbers.

    • Their primary identifier in KYC Flow is the LegalPersonId

Participant

An assignment can include a whole bunch of Legal and Physical persons, but the “Participants” in an assignment are considered the most important. A Participant can be defined as:

  • Any Legal or PhysicalPerson with any AssignmentRoles (Client, Buyer, Seller, Customer, etc) when read from the assignment Details endpoint: api/v2/assignment/{assignmentId}/details

In the UI, these are presented as the most important people in an Assignment.

A Participant has Monitor enabled by default, and will have a lookup against the KYC API performed automatically when they are added to an assignment.

Webforms

KYC Flow has functionality where you can send out webforms for end-customers and clients to fill out.

These webforms are based on webform Templates that a user can create in the Editor.

Some of the basic questions that are included by default in a webform is “Are you PEP?” and “what roles does this person have in the company?” with a list of common and toggleable roles.

Risk Assessments

A typical requirement for a customer with AML and KYC obligations is to perform a Risk Assessment. This often includes

Risk assessments can be performed on each Participant in the Assignment, and on the Assignment itself.

Checklists

UNDER CONSTRUCTION.

Hello World” example

DRAFT: UNDER CONSTRUCTION, AS OF 21.04.2026 PLEASE HOLD

I want an assignment with 1 Physical Person

You just want to get started with Physical Persons and get something useful inside of KYC Flow.

{ "name": "{DESIRED_ASSIGNMENT_NAME}", // Typically you want this to the the customer name or Legal Person name "reference": "{SOME_HUMAN_READABLE_VALUE}", "referenceCode": "{SOME_MACHINE_READABLE_VALUE}", //This value is sent with all Webhooks from KYC Flow, so you can use it to bind an assignment to your internal system "participants": [ { "role": "Client", // Valid values are currently "Client", "Buyer", "Seller" and "Customer", but this is just a flavour text: its how you decide to think about your customer "physicalPersons": [ { "firstName": "Tomas", "lastName": "Topstad", "dob": "1985-01-01", //yyyy-MM-dd "country":"NO", "nationalIdNumber": "01018549928", "mobile": null, "email": null }], } ], // processTemplateId determines if this is going to be a SIMPLE assignment with 1 participant (the default case if you supply Null). If you want to be explicit the identifier is: "00000000-0000-0000-0000-000000000000" // or if it is going to be a Multi-particiant assignment: "00000000-0000-0000-0000-000000000001" // Over time you will be able to define custom process templates with unique IDs. "processTemplateId": null, // We are deprecating "process" in favour of processTemplateIds, but because it is REQUIRED in the API, just include it for now. It is not actually used "process": [ { "step": 0, "name": "DEFAULT", "process": [ "ORGLOOKUP"]}] }

If you just want to make a successful API call as a sanity check before you start writing any code, you can login to KYC Flow and paste this little bit of javascript in your browser console. Be sure to remain on the right domain, or you will get CORS errors.

let hostname = window.location.hostname await fetch("https://" + hostname + "/api/v2/assignments", { method: "POST", headers:{"Content-Type":"application/json"}, body: JSON.stringify({"name": "Test Assignment 1", "reference": "Test assignment 1", "referenceCode": "TEST_ASSIGNMENT_1", "participants": [ { "role": "Client", "physicalPersons": [ { "firstName": "Tomas", "lastName": "Topstad", "dob": "1985-01-01", "country":"NO", "nationalIdNumber": "01018549928", "mobile": null, "email": null }] } ], "processTemplateId": null, "process": [ { "step": 0, "name": "DEFAULT", "process": [ "ORGLOOKUP"]}] })})

I want an assignment with 1 Legal Person

let hostname = window.location.hostname await fetch("https://" + hostname + "/api/v2/assignments", {method: "POST", headers:{"Content-Type":"application/json"}, body: JSON.stringify({"name": "ORG2","reference": "2",referenceCode: "2","participants": [ { "role": "Client", "legalPersons": [ { "organizationNumber": "767448562","country":"NO","name": "Norway Agency","contactPersons":[]}], "physicalPersons": [ ] } ], "process": [ { "step": 0, "name": "Placeholder", "process": [ "ORGLOOKUP"]}], "processTemplateId": "00000000-0000-0000-0000-000000000000"})})
image-20260421-094914.png

Webhooks explained

We have a simple philosophy for webhooks:

  • Send them for anything that a user could deem important.

  • Include minimal info: rely primarily on GUIDS to designate what resource they relate to.

    • This allows us to quickly add new webhooks based on customer needs. If there is an obvious webhook you need for an event that already occurs in KYC Flow, don’t hesitate to ask.

    • Webhooks are intentionally kept bare so that the complexity remains in documented API endpoints, and not in an ever-expanding list of webhooks.

  • Optionally include links when there might be an obvious resource (like a PDF or Webform) that the webhook consumer could use for an integration.

  • ALL webhooks are related to an assignment, and includes an AsssignmentId and a referenceCode. Use this to correlate with your customer in the internal systems.

    • MANY webhooks also include a GUID related to a sub-resource (like PhysicalPersonId and LegalPersonId ) but they are always in the context of an Assignment.

  • The name of the webhook should explain what happened, and include identifiers to describe where and what that thing happened to.

  • A webhook is almost never enough in itself. You typically need to make an extra API call to get the info you need, if you want to do some extra action.

  • AssignmentWebhookDto is ALWAYS the correct object to deserialize.

Integration recipies

A Recipe is just a typical integration architecture and pattern we recommend.

Cross-platform Assignment Notifications and status Sync

Usecase: You have some way of keeping track of customers in an existing system. You dont want to log into KYC Flow daily unless you need to, because your primary UI for managing customers shows other important info. But you want your existing UI to show that something needs to be done in KYC Flow.

Requirements:

  • This existing system supports a “Status” field or notification field visible to the user.

Structure

  • Create an assignment with the POST /api/v2/assignment endpoint.

  • Anytime you receive a webhook, check the GET /api/v2/assignment/{assignmentId}/timeline endpoint. Note: if you subscribe to all webhooks, this will happen often. Considder if you want to check every time.

    • If there is anything in the waitingForUser array, it means that the user has an unfinished action they need to perform in KYC Flow.

    • If there is anything in the waitingForExternal array, it means the assignment is waiting for one of your customers to finish something (like an ID confirmation or a webform)

  • Use the description field for a human-readable text blob you can show the user, and optionally a link that takes you straight to the assignment. This wont typically need to change: https://{KYC_FLOW_URL}/aml/v2/assignments/{assignmentId}

Integrated Webform link

Usecase: You do not want to KYC Flow to send emails for webforms, but you would rather expose the Access Link to the Webform in your App or website directly in front of the customer.

Requirements

  • Your system must have a way of exposing a clickable link to the end user in a per-user way.

Setup

Subscribe to

  • WEBFORM_EMAIL_REMINDER_READY_TO_SEND

  • WEBFORM_EMAIL_READY_TO_SEND

Structure

  1. Whenever you receive the webhook WEBFORM_EMAIL_READY_TO_SEND or WEBFORM_EMAIL_REMINDER_READY_TO_SEND, extract webformIdfrom Details.

  2. Call GET api/v2/links/webforms/{webformId} endpoint to get Links to send to the recipient, as well as name and contact information.1

  3. Expose this link to the customer. This is a link with Authentication, which means whoever clicks it will get a cookie giving them access to the webform.

You now have a way to give users access to webforms intended only for them.

 

1 Note that the webhook itself also has relevant links in the _links object, but these are harder to parse beacuse they are not auto-generated from the OpenAPI spec. Recommend using the api/v2/links/webforms/ endpoint instead.

 

PEP updates

Usecase: you would like to know anytime a PEP status changes within the Assignment.

KYC Flow does not currently have a “clean” single webhook to listen to to mention that the total PEP has changed, but it has a collection of different nuanced scenarios that can lead to a PEP status being updated.

As a general strategy, we recommend a two thronged approach:

  1. Pay attention to webhooks that notify you whenever a user is required to verify, validate or dismiss a PEP alert or PEP source.

  2. Pay attention to webhooks that indicate that a PEP status changed.

By doing it this way,

API Authentication

Static API token

The simplest way to get started is with a static Bearer token.

Notes:

  • This static API Key has access to EVERYTHING in the KYC Flow Instance: There is no granular control.

  • This is not recommended in production, but is much quicker to provision and start testing with.

GET https://{YOUR_UNIQUE_SUBDOMAIN}.ksp-stoedev.cloud/api/v2/assignments Authorization Bearer {API_KEY}

 

Client Credentials flow with OIDC

The typical Client Credentials flow which requires a ClientSecret and ClientId.

Notes

  • This allows for more granular access control.

  • The token can be given scopes and audience to interact with multiple KYC Flow instances as desired.

Webhook list

You should prefer to trust the list provided by KYC Flow OpenAPI spec since it is updated more frequently, but here is a conveniently accessible list of all webhooks currently served by KYC Flow as of 23.06.2026

ASSIGNMENT_CREATED, LEGAL_PERSON_CREATED, LEGAL_PERSON_NAME_UPDATED, LEGAL_PERSON_ADDED_TO_ASSIGNMENT, LEGAL_PERSON_PARTICIPANT_ADDED_TO_ASSIGNMENT, ASSIGNMENT_ADDED_TO_LEGAL_PERSON, PHYSICAL_PERSON_PARTICIPANT_ADDED_TO_ASSIGNMENT, ASSIGNMENT_PARTICIPANT_MODIFIED, CONTACT_PERSON_ADDED_TO_ASSIGNMENT, WEBFORM_EMAIL_READY_TO_SEND, WEBFORM_EMAIL_REMINDER_READY_TO_SEND, ID_CHECK_SENT, ID_CHECK_SUCCESSFUL, ID_CHECK_DOCUMENT_SIGNED, ID_CHECK_FAILED, ID_CHECK_DECLINED, ID_CHECK_CANCELLED, ID_CHECK_READY_TO_SEND, ID_CHECK_REMINDER_READY_TO_SEND, ASSIGNMENT_WAITING_FOR_RISK_TO_UPDATE, ASSIGNMENT_UNEXPECTED_PHYSICAL_PERSON_ID_CONFIRMED, ASSIGNMENT_UNEXPECTED_PHYSICAL_PERSON_ID_ACCEPTED, ASSIGNMENT_UNEXPECTED_PHYSICAL_PERSON_ID_DECLINED, ASSIGNMENT_CONTACT_PERSON_ROLE_REMOVED, ASSIGNMENT_POSSIBLE_PEP_MATCH_START_FOR_PHYSICAL_PERSON, ASSIGNMENT_POSSIBLE_PEP_MATCH_END_FOR_PHYSICAL_PERSON, ASSIGNMENT_POSSIBLE_SANCTION_MATCH_START_FOR_PHYSICAL_PERSON, ASSIGNMENT_POSSIBLE_SANCTION_MATCH_END_FOR_PHYSICAL_PERSON, ASSIGNMENT_POSSIBLE_SANCTION_MATCH_START_FOR_LEGAL_PERSON, ASSIGNMENT_POSSIBLE_SANCTION_MATCH_END_FOR_LEGAL_PERSON, ASSIGNMENT_RISK_UPDATED, ASSIGNMENT_LEGAL_PERSON_RISK_UPDATED, ASSIGNMENT_PHYSICAL_PERSON_RISK_UPDATED, ASSIGNMENT_OFFBOARDING_INITIATED, ASSIGNMENT_PHYSICAL_PERSON_MONITOR_START, ASSIGNMENT_PHYSICAL_PERSON_MONITOR_END, ASSIGNMENT_LEGAL_PERSON_MONITOR_START, ASSIGNMENT_LEGAL_PERSON_MONITOR_END, ASSIGNMENT_NAME_MODIFIED, WEBFORM_SIGNED, WEBFORM_CANCELLED, UNEXPECTED_PERSON_SIGNED, ALERT_ACCEPTED, ALERT_DECLINED, ASSIGNMENT_PROCESS_NOTIFICATION_ACKNOWLEDGED, ASSIGNMENT_PHYSICAL_PERSON_AML_PEP_SOURCE_ACCEPTED, ASSIGNMENT_PHYSICAL_PERSON_AML_PEP_SOURCE_DECLINED, ASSIGNMENT_PHYSICAL_PERSON_AML_SANCTION_SOURCE_ACCEPTED, ASSIGNMENT_PHYSICAL_PERSON_AML_SANCTION_SOURCE_DECLINED, ASSIGNMENT_LEGAL_PERSON_AML_SANCTION_SOURCE_ACCEPTED, ASSIGNMENT_LEGAL_PERSON_AML_SANCTION_SOURCE_DECLINED, ASSIGNMENT_PHYSICAL_PERSON_MARKED_AS_PEP_BY_USER, ASSIGNMENT_PHYSICAL_PERSON_MARKED_AS_NOT_PEP_BY_USER, ASSIGNMENT_PHYSICAL_PERSON_MARKED_AS_SANCTIONED_BY_USER, ASSIGNMENT_PHYSICAL_PERSON_MARKED_AS_NOT_SANCTIONED_BY_USER, ASSIGNMENT_PHYSICAL_PERSON_ROLES_UPDATED_MANUALLY, ASSIGNMENT_LEGAL_PERSON_ROLES_CHANGED, WEBFORM_SIGNED_BY_UNEXPECTED_PERSON, WEBFORM_SIGNED_BY_UNEXPECTED_PERSON_ACCEPTED, WEBFORM_SIGNED_BY_UNEXPECTED_PERSON_DECLINED, ASSIGNMENT_LEGAL_PERSON_AML_REPORT_RECEIVED, ASSIGNMENT_LEGAL_PERSON_INCORPORATION_REPORT_RECEIVED, MANUAL_ID_CHECK_COMPLETED, LEGAL_PERSON_RISK_INDICATORS_UPDATED, PHYSICAL_PERSON_RISK_INDICATORS_UPDATED, ASSIGNMENT_PHYSICAL_PERSON_AML_REPORT_RECEIVED, ID_CHECK_TIMED_OUT, LEGAL_PERSON_KEY_INFORMATION_UPDATED, SYNC_ASSIGNMENT, AMS_STARTED_FOR_LEGAL_PERSON, AMS_REPORT_RECEIVED_FOR_LEGAL_PERSON, AMS_JSON_DATA_RECEIVED_FOR_LEGAL_PERSON, AMS_FAILED_FOR_LEGAL_PERSON, AMS_STARTED_FOR_PHYSICAL_PERSON, AMS_REPORT_RECEIVED_FOR_PHYSICAL_PERSON, AMS_JSON_DATA_RECEIVED_FOR_PHYSICAL_PERSON, AMS_FAILED_FOR_PHYSICAL_PERSON, WEBFORM_DOCUMENT_SIGNED, KAR_LOOKUP_PERFORMED, ASSIGNMENT_RESPONSIBLE_USERS_UPDATED, PHYSICAL_PERSON_ADDED_TO_ASSIGNMENT_WITH_EXISTING_AML_SOURCES