How KYC works

Overview of the Know Your Customer verification process, webhook events, Knowledge Based Authentication, and retrieving the KYC status.

KYC process overview

After your customer has provided the information necessary to complete an onboarding flow, you must authenticate and verify that information.

This is a critical step, known as KYC in which you validate your customer's identity, documents, and ensure they're compliant with federal regulations.

▶ Run in Postman

Using the Customer object, you can perform a host of checks and screenings to ensure compliance programmatically. The full KYC process is shown below.

As part of the KYC check, you might receive a webhook event (kyc.verification.document_required) asking for documents to be uploaded for validation. This might happen, for example, when a customer’s name does not match with the name in an SSN lookup. For details regarding how to upload documents, see KYC document upload.

Idempotency

📘

Note

The KYC endpoint is idempotent and repeated requests using the same Idempotency-Key within a 24 hour period will fail.

Once a customer has successfully passed the KYC process, no further KYC attempts are allowed. Any further call for KYC authentication responds with an error and returns the timestamp of the previously successful KYC process.

Idempotency is a Web API design principle that prevents you from running the same operation multiple times. Because a certain amount of intermittent failure is to be expected, you need a way to reconcile failed requests with a server, and idempotency provides a mechanism for that. Including an idempotency key makes POST requests idempotent, which prompts the API to do the record keeping required to prevent duplicate operations. You can safely retry requests that include an idempotency key as long as the second request occurs within 24 hours from when you first receive the key (keys expire after 24 hours).

Providing the idempotency key string in the header is optional and example is shown below.

{
   "Authorization": "YOUR-AUTHORIZATION",
   "Identity": "YOUR-IDENTITY",
   "Idempotency-Key": "dd6dcedd-2a11-4098-1223-876902123abc"
}

📘

Note

Most of a customer's details can't be updated after they've passed KYC as the new information needs to be verified before the update can occur. If you need to update information for a customer that has passed KYC, contact Bond directly.
For details, see Updating a customer.

Starting a KYC process

To initiate a KYC request, use the POST /customers/{customer_id}/verification-kyc operation and provide the parameters as shown in the table below.

ParameterTypeDescription
program_id
required
stringProgram UUID, for example 72585109-8222-4221-b15b-48e87ffed790.
ssnstringSocial security number of the form ######### or ###-##-####, for example 123-45-6789.
phonestringNumeric or numeric/dashes, for example 555-111-2222.
phone_country_codestringNumeric, between 1 and 3 digits, for example 325.
emailstringValid email address, for example [email protected]
ip
conditionally required based on program configuration
stringValid IPV4 dot-separated address, for example 192.168.1.139.
Can be conditionally required based on program_id.

The combination of customer_id and program_id in the request indicates that Bond will do a verification check on a particular customer on behalf of a particular bank partnered with the brand.

The KYC request for the customer is asynchronous (meaning that the reply is not always immediate), so you must configure a webhook to listen for KYC events.

An example of a KYC request is shown below.

curl --request POST \
     --url https://sandbox.bond.tech/api/v0.1/customers/2df10ec1-130f-41bb-b0cf-f3af48350eb7/verification-kyc \
     --header 'Accept: application/json' \
     --header 'Authorization: YOUR-AUTHENTICATION' \
     --header 'Content-Type: application/json' \
     --header 'Identity: YOUR-IDENTITY' \
     --data '
{
     "program_id": "72585109-8222-4221-b15b-48e87ffed790",
     "ip": "24.54.101.22"
}
'
require 'uri'
require 'net/http'
require 'openssl'

url = URI("https://sandbox.bond.tech/api/v0.1/customers/2df10ec1-130f-41bb-b0cf-f3af48350eb7/verification-kyc")

http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true

request = Net::HTTP::Post.new(url)
request["Accept"] = 'application/json'
request["Content-Type"] = 'application/json'
request["Identity"] = 'YOUR-IDENTITY'
request["Authorization"] = 'YOUR-AUTHENTICATION'
request.body = "{\"program_id\":\"72585109-8222-4221-b15b-48e87ffed790\",\"ip\":\"24.54.101.22\"}"

response = http.request(request)
puts response.read_body
const options = {
  method: 'POST',
  headers: {
    Accept: 'application/json',
    'Content-Type': 'application/json',
    Identity: 'YOUR-IDENTITY',
    Authorization: 'YOUR-AUTHENTICATION'
  },
  body: JSON.stringify({program_id: '72585109-8222-4221-b15b-48e87ffed790', ip: '24.54.101.22'})
};

fetch('https://sandbox.bond.tech/api/v0.1/customers/2df10ec1-130f-41bb-b0cf-f3af48350eb7/verification-kyc', options)
  .then(response => response.json())
  .then(response => console.log(response))
  .catch(err => console.error(err));
import requests

url = "https://sandbox.bond.tech/api/v0.1/customers/2df10ec1-130f-41bb-b0cf-f3af48350eb7/verification-kyc"

payload = {
    "program_id": "72585109-8222-4221-b15b-48e87ffed790",
    "ip": "24.54.101.22"
}
headers = {
    "Accept": "application/json",
    "Content-Type": "application/json",
    "Identity": "YOUR-IDENTITY",
    "Authorization": "YOUR-AUTHENTICATION"
}

response = requests.post(url, json=payload, headers=headers)

print(response.text)
var client = new RestClient("https://sandbox.bond.tech/api/v0.1/customers/2df10ec1-130f-41bb-b0cf-f3af48350eb7/verification-kyc");
var request = new RestRequest(Method.POST);
request.AddHeader("Accept", "application/json");
request.AddHeader("Content-Type", "application/json");
request.AddHeader("Identity", "YOUR-IDENTITY");
request.AddHeader("Authorization", "YOUR-AUTHENTICATION");
request.AddParameter("application/json", "{\"program_id\":\"72585109-8222-4221-b15b-48e87ffed790\",\"ip\":\"24.54.101.22\"}", ParameterType.RequestBody);
IRestResponse response = client.Execute(request);
OkHttpClient client = new OkHttpClient();

MediaType mediaType = MediaType.parse("application/json");
RequestBody body = RequestBody.create(mediaType, "{\"program_id\":\"72585109-8222-4221-b15b-48e87ffed790\",\"ip\":\"24.54.101.22\"}");
Request request = new Request.Builder()
  .url("https://sandbox.bond.tech/api/v0.1/customers/2df10ec1-130f-41bb-b0cf-f3af48350eb7/verification-kyc")
  .post(body)
  .addHeader("Accept", "application/json")
  .addHeader("Content-Type", "application/json")
  .addHeader("Identity", "YOUR-IDENTITY")
  .addHeader("Authorization", "YOUR-AUTHENTICATION")
  .build();

Response response = client.newCall(request).execute();

The results of the KYC will be sent via webhook events. For information on subscribing to Bond platform webhook events, see Event subscriptions and Accepting webhook requests.

A example of a KYC initialized response is shown below.

{
  "customer_id": "2df10ec1-130f-41bb-b0cf-f3af48350eb7",
  "kyc_status": "submitted"
}

Examples of KYC responses are shown below.

{
    "event": "kyc.verification.success",
    "customer_id": "a5bcf5a8-c4e0-4025-8183-5346176ee3db",
    "occurred_at": "2021-02-02-00:50:58.484840+00:00"
}
{
    "event": "kyc.verification.failure",
    "customer_id": "a5bcf5a8-c4e0-4025-8183-5346176ee3db",
    "occurred_at": "2021-02-02-00:50:58.484840+00:00"
}
{
    "event": "kyc.verification.error",
    "customer_id": "a5bcf5a8-c4e0-4025-8183-5346176ee3db",
    "occurred_at": "2021-02-02-00:50:58.484840+00:00"
}
{
    "event": "kyc.verification.timeout",
    "customer_id": "a5bcf5a8-c4e0-4025-8183-5346176ee3db",
    "occurred_at": "2021-02-02-00:50:58.484840+00:00"
}
{
    "event": "kyc.verification.document_required",
    "customer_id": "a5bcf5a8-c4e0-4025-8183-5346176ee3db",
    "occurred_at": "2021-02-02-00:50:58.484840+00:00",
    "documents":[
      {
        "document_type": "government_id",
        "upload_link": "https://withpersona.com/verify?template-id=tmpl_111111111111111111111111&reference-id=a5bcf5a8-c4e0-4025-8183-5346176ee3db",
        "status": "required"
      },
      {
        "document_type": "utility_bill",
        "upload_link": "https://withpersona.com/verify?template-id=tmpl_222222222222222222222222&reference-id=a5bcf5a8-c4e0-4025-8183-5346176ee3db",
        "status": "required"
      },
      {
        "document_type": "social_security_card",
        "upload_link": "https://withpersona.com/verify?template-id=tmpl_333333333333333333333333&reference-id=a5bcf5a8-c4e0-4025-8183-5346176ee3db",
        "status": "required"
      }, 
    ]
}
{
  "customer_id": "ef096b64-7409-40cd-9879-badd905dc6a5",
  "event": "kyc.verification.reenter_information",
  "description": "Re enter the below information for this customer's KYC attempt on program a2a0f2fc-ea8f-4ada-ad22-a1fbb0ca138a",
  "fields": [
    "first_name", 
    "last_name",
    "ssn"
  ]
}

After a customer has been successfully vetted, further KYC attempts are not be allowed. The KYC service responds with an error and displays the timestamp of the previous successful KYC request.

For a complete specification and interactive examples, see Starting the KYC process in the Bond API Reference.

KYC webhook events

We provide a kyc.verification.status passed/failed response to the callback_url configured in the webhook. If the kyc.verification.status is passed, the customer's information has been validated and they are eligible to access the services provided by the bank. You can now create a card for the customer.

Event valuesDescription
kyc.verification.errorKYC failed to complete due to server error. Send a POST /customer/{customer_id}/verification-kyc to retry
kyc.verification.failureKYC failed due to low confidence in identity validation
kyc.verification.successKYC passed
kyc.verification.timeoutKYC failed to complete due to connection timeout to the KYC vendor (Persona). Send a POST /customer/{customer_id}/verification-kyc request to retry.

If the customer does not upload documents within 28 days, the customer's KYC attempt expires and a kyc.verification.timeout webhook is sent to any subscribed listeners. A new KYC must be initialized for customers when this happens.

You can differentiate between the two webhook cases by checking if the kyc.verification.document_required webhook was sent and/or by tracking completed KYC process steps using a flag or timestamp on your backend.
kyc.verification.document_requiredKYC requires further information to continue. This includes a documents field that indicates the types of documents required. See documents required.
kyc.verification.under_reviewKYC documents that have been submitted are under review.
kyc.verification.reenter_informationThis is optional and will be sent at the same time as kyc.verification.document_required

Customer might have entered information incorrectly, and can potentially resolve the exception by reentering their information. This includes an 'incorrect_information' field that indicates the specific piece of information that should be checked by the customer.

📘

kyc.verification.timeout events

To distinguish between timeout events due to connection issues vs. document upload expiry timeouts, you may need to check to see if a kyc.verification.document_required webhook was sent for that customer and have some sort of flag or timestamp on your backend to track completion of KYC process steps. Worth noting, however, is that whether the document_required webhook is sent depends on how the customer failed or abandoned their KYC process. Some customers can just abandon their KYC without reaching the document collection step, in which case their KYC will expire without the document_required webhook being sent.

Sometimes these events may include a description field in the payload prompting further action, as shown in the "PO Box" failure example below that requests a new address.

{
    "event": "kyc.verification.failure",
    "customer_id": "a5bcf5a8-c4e0-4025-8183-5346176ee3db",
    "occurred_at": "2021-02-02-00:50:58.484840+00:00",
    "error_code": "po_box_failure",
    "description": "Customer address is a PO Box. Request a new address"
}

For further information, see Webhooks and Webhook events and subscriptions.

For a complete specification and interactive examples, see Create webhook subscription in the Bond API Reference.

Retrieving the KYC status

To retrieve the KYC status for a customer, use the GET /customer/{customer_id}/verification-kyc operation and provide the following parameter.

ParameterDescription
program_id
required
Unique program identifier (credit/debit, and so on) for a brand.

An example of a request to retrieve the KYC status is shown below.

curl --request GET \
     --url 'https://sandbox.bond.tech/api/v0.1/customers/931e2341-c3eb-4681-97d4-f6e09d90da14/verification-kyc?program_id=72585109-8222-4221-b15b-48e87ffed790' \
     --header 'Authorization: YOUR-AUTHORIZATION' \
     --header 'Identity: YOUR-IDENTITY'
require 'uri'
require 'net/http'
require 'openssl'

url = URI("https://sandbox.bond.tech/api/v0.1/customers/931e2341-c3eb-4681-97d4-f6e09d90da14/verification-kyc?program_id=72585109-8222-4221-b15b-48e87ffed790")

http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true

request = Net::HTTP::Get.new(url)
request["Identity"] = 'YOUR-IDENTITY'
request["Authorization"] = 'YOUR-AUTHORIZATION'

response = http.request(request)
puts response.read_body
const options = {
  method: 'GET',
  headers: {
    Identity: 'YOUR-IDENTITY',
    Authorization: 'YOUR-AUTHORIZATION'
  }
};

fetch('https://sandbox.bond.tech/api/v0.1/customers/931e2341-c3eb-4681-97d4-f6e09d90da14/verification-kyc?program_id=72585109-8222-4221-b15b-48e87ffed790', options)
  .then(response => response.json())
  .then(response => console.log(response))
  .catch(err => console.error(err));
import requests

url = "https://sandbox.bond.tech/api/v0.1/customers/931e2341-c3eb-4681-97d4-f6e09d90da14/verification-kyc"

querystring = {"program_id":"72585109-8222-4221-b15b-48e87ffed790"}

headers = {
    "Identity": "YOUR-IDENTITY",
    "Authorization": "YOUR-AUTHORIZATION"
}

response = requests.request("GET", url, headers=headers, params=querystring)

print(response.text)
var client = new RestClient("https://sandbox.bond.tech/api/v0.1/customers/931e2341-c3eb-4681-97d4-f6e09d90da14/verification-kyc?program_id=72585109-8222-4221-b15b-48e87ffed790");
var request = new RestRequest(Method.GET);
request.AddHeader("Identity", "YOUR-IDENTITY");
request.AddHeader("Authorization", "YOUR-AUTHORIZATION");
IRestResponse response = client.Execute(request);
OkHttpClient client = new OkHttpClient();

Request request = new Request.Builder()
  .url("https://sandbox.bond.tech/api/v0.1/customers/931e2341-c3eb-4681-97d4-f6e09d90da14/verification-kyc?program_id=72585109-8222-4221-b15b-48e87ffed790")
  .get()
  .addHeader("Identity", "YOUR-IDENTITY")
  .addHeader("Authorization", "YOUR-AUTHORIZATION")
  .build();

Response response = client.newCall(request).execute();

The response includes the overall status of the KYC and failure reasons if applicable, as shown in the example below.

{
    "customer_id": "d856bf10-d4a6-4153-b8f8-2b93072dd8da",
    "brand_id": "ce027c9c-39c6-47f4-a12d-abb543f7cf63",
    "kyc_status": "passed"
}

The following table provides a list of possible KYC statuses.

StatusDescription
passedSuccessful KYC. Signaled by the kyc.verification.success webhook event.
failedUnsuccessful KYC. Signaled by the kyc.verification.failure event.
document requiredKYC requires additional supporting information. Signaled by the kyc.verification.document_required event.
under reviewKYC has been flagged for further review. Signaled by the kyc.verification.under_review webhook.
errorKYC request has encountered a server error. Signaled by the kyc.verification.error webhook. Use the Start KYC endpoint to retry.
expiredKYC request has encountered a networking error. Signaled by the kyc.verification.timeout webhook. Use the Start KYC endpoint to retry.

For a complete specification and interactive examples, see Retrieving KYC status in the Bond API Reference.

KYC exceptions

Documents required

A KYC process may require one or more documents to be submitted. When this happens, a webhook is sent with the type of document required and the KYC status is set to kyc.verification.document_required. For details on uploading documents, see KYC document upload.

Depending on whether the documents are approved or they need to be reviewed, the status changes to either kyc.verification.success or kyc.verification.under_review respectively, and a webhook is sent.

Documents under review

After you upload the required documents and they need to be reviewed, a webhook is sent and the KYC status is set to kyc.verification.under_review. You don’t need to take any further action. Once the review is complete, a webhook is sent and the status is updated to either kyc.verification.success or kyc.verification.failure.

Reenter Information

In some cases, you will receive a webhook to reenter KYC information at the same time as the documents required webhook. This indicates that the user may have mistakenly entered incorrect information, and the exception can be resolved if they make a correction. You can still follow the Documents required path if you would like.

We recommend showing the customer the information that might need to be changed in your UI. If they change that information, call the Update a Customer endpoint to update that in Bond, then submit the KYC again.

In the event of an SSN being corrected by the customer, you do not need to use the Update a Customer endpoint. You can just submit the KYC again using the updated SSN.