Webhooks and Event Queue

Webhooks and Event Queue

Bakkt's event notification system is designed to provide clients with timely updates that are essential for digital asset trading and account management. Bakkt utilizes a multifaceted notification system to navigate the digital asset market efficiently, including Amazon Web Services (AWS) Simple Queue Service (SQS), webhooks, and websockets to deliver a variety of notifications.

Overview

Event notification types include:

  • Webhooks: Facilitate notifications and updates for customer account creation and fiat onramp integration.
  • Amazon Web Services (AWS) Simple Queue Service (SQS): Automates REST order management notifications, including digital asset orders, transfers, batch trade and allocations, and gifting rewards.
  • Websockets: Provide live market data, essential for real-time trading. For guidance on integrating websockets for market data notifications, consult the Market Data Feed documentation.

Subsequent sections will focus on integrating AWS SQS and webhooks.

Delivery via SQS

Bakkt provides clients with a durable Event Queue to which they can connect and receive updates on digital asset orders, transfers, batch trade and allocations, and gifting rewards. This implementation leverages the Amazon Web Service (AWS) Simple Queue Service (SQS).

Many of the Bakkt APIs rely on workflows with event notifications to client applications. The following information provides specific aspects of the Event Queue implementation.

Core Concepts and Functionality

The Bakkt AWS SQS provides an easy and reliable means of message transmission. The Amazon Simple Queue Service provides the basic AWS documentation to assist in implementing the Bakkt Event Queue. The mechanics of the SQS ensure the reliability of event delivery. Bakkt retains events sent to the queue for five days or when the system acknowledges receipt of the event. As part of the process of receiving events from the SQS, clients must acknowledge when events successfully process to ensure the system delivers all events at least once. Clients can use the following workflow to implement the Event Queue.

Workflow

Connect to the Event Queue

  1. Client systems must authenticate to the Bakkt AWS SQS site using the same credentials they use for their AWS S3 document management system as explained in the AWS Credential Setup instructions. Bakkt recommends that clients use long polling to know when they have a message to process.

Bakkt provides clients with queue URLs to find and receive messages. Bakkt provides a queue URL in each of its DR failover regions. The QueueUrl definition from AWS is the URL of the Amazon SQS, and the type is String. Clients can see the Disaster Recovery (DR) section below for best-practice information.

Receive and Acknowledge Messages from the Event Queue

  1. When messages are available in the queue (via long polling), clients must receive and process the messages from the Event Queue. SQS protocol dictates that clients process messages in their applications and then delete the messages from the SQS Event Queue to acknowledge when they receive and process the messages. The AWS documentation explains this process in SQS.

Event Queue Notification Message

  1. The Bakkt Event Queue does not utilize all available fields in the SQS message protocol. The body of the SQS messages includes all of the necessary Bakkt notification information. Client systems do not have to use any other available fields in the SQS message. The following fields are available in Bakkt Event Notifications.
FieldTypeDescription
message_idUUIDEach message has a unique identifier (ID) when Bakkt sends it to the Event Queue; clients use this ID when they receive messages to determine if they already processed them. Bakkt strives to send each message only one time; however, there are certain race conditions where the system may send duplicate messages, so clients should build applications to prevent duplicate processing of the same events.
event_typeStringThis field is in the structured format for the message that indicates the domain, schema, and version; the domain tells clients the Bakkt system sending the event. Current domains include, but are not limited to, orders, transfers, accounts, and allocation; the schema provides the structure of the message; each event domain has its own set of schemas in the open API specification; the version is the open API specification version. Bakkt supports backwards compatibility; as versions of a schema change, the system can send multiple versions of the same message.
timestampStringThis field is the timestamp for event publishing; this is not for event triggering.
payloadObjectThis object is the event object for the specific event notification.
message_group_idStringThe Message Group ID assigns the partition key to preserve ordering of the messages within that key. Some example use cases are: Client Account ID - Many of the Bakkt systems perform actions related to an investor account number; in these applications, the account is the message_group_id to allow the Bakkt client to better order investor account-related events. Client Order ID - The order ID the client system assigns to the submitted order.

Disaster Recovery and Event Queues

Each AWS region presents a single SQS queue—one for primary use and one for Bakkt's disaster recovery footprint. The status update events are available in each queue with a standard envelope wrapping each event to describe the event type. The SQS event stream does not include market data events due to their volume and non-durable nature.

Warning: Bakkt highly recommends that clients consume both the primary and disaster recovery queues at all times. The Event system removes events after clients receive them which brings both queues into a contemporary state. Since the system publishes all events to both queues, the process must use the deduplication identifier to ensure that the system only processes events once. If clients only consume the primary queue, they must receive and retain five days worth of events in case of region failovers.

Connecting to AWS SQS

Clients can use the following process to connect to the Amazon Web Service Simple Queue Service (AWS SQS).

Note: Some events are not enabled by default. If you need any particular events, please let us know.

Step-by-Step Workflow

Step 1: Use the standard AWS options to allow authentication using the typical environment variables.

func example() {
  logger := log.Default()
  
  awsSessionOptions := session.Options{
    SharedConfigState: session.SharedConfigEnable,
  }
  awsSession := session.Must(session.NewSessionWithOptions(awsSessionOptions))

Step 2: Create the SQS client.

sqsService := sqs.New(awsSession, aws.NewConfig())

  queueUrl :=
"https://sqs.us-east-2.amazonaws.com/{AWSID}/uat-{corr}-events.fifo"

Step 3: Define the parameters for the ReceiveMessage call. The WaitTimeSeconds option enables long-polling (which Bakkt recommends).

for {
    sqsReceiveMessageInput := sqs.ReceiveMessageInput{
        QueueUrl: &queueUrl,
        WaitTimeSeconds: aws.Int64(10),
    }

Step 4: Receive message(s) from the AWS SQS queue.

sqsReceiveMessageOutput, err :=
sqsService.ReceiveMessage(&sqsReceiveMessageInput)
       if err != nil {
           logger.Fatal(err)
       }

Step 5: Loop through each received message. The array is empty if no messages are available. Extract the body from the SQS message and unmarshal the SQS message body.

for _, sqsMessage := range sqsReceiveMessageOutput.Messages {

    sqsMessageBody := sqsMessage.Body
    
    unMarshalledBody := make(map[string]interface{})
    err := json.Unmarshal([]byte(*sqsMessageBody), &unMarshalledBody)
    if err != nil {
        log.Fatal(err)
    }

Step 6: Process the message. In the following example, Bakkt prints the body as JSON.

formattedBody, err := json.MarshalIndent(unMarshalledBody, "", " ")
if err != nil {
    log.Fatal(err)
}
fmt.Println(string(formattedBody))

Step 7: Define the parameters for the DeleteMessage call. The third line contains a unique reference to delete a received SQS message.

sqsDeleteMessageInput := sqs.DeleteMessageInput{
    QueueUrl: &queueUrl,
    ReceiptHandle: sqsMessage.ReceiptHandle,
}

Step 8: Acknowledge that the message processes by deleting it from the SQS queue.

_, err = sqsService.DeleteMessage(&sqsDeleteMessageInput)
      if err != nil {
        logger.Fatal(err)
      }
    }
  }
}

Delivery via Webhooks

Bakkt uses webhooks for account creation and fiat onboarding notifications, providing detailed updates on account status, transactions, verification requirements, and more. This guide covers how to respond and understand these notifications for effective platform management.

Account Opening Webhook Events

When opening accounts, clients must capture the data in the webhook events to receive detailed status notifications. These notifications are published to the client's webhook URL that was provided to Bakkt during onboarding. These notifications will provide clients with various updates on the account's status, including successful account creation, failure, suspension, requests for additional documentation, and more.

The client's webhook URL will receive a payload that includes these key elements, each providing insight into a specific aspect of the account:

  • requestId: A unique Identifier for the request.
  • partnerPartyRef: The partner's unique identifier linked to a customer's account.
  • partnerWebhookType: Specifies the notification type, here as PARTY_EVENT.
  • partyStatus: The account's current state, with possible values being ACTIVE, INACTIVE, SUSPENDED, LIQUIDATED, UNDER_REVIEW, and CLOSED.
  • reasonCode: Categorizes the rationale behind the current account status.
  • verificationURL: A link for uploading documents to validate customer identity.

Accompanying these elements, the reason codes will detail the circumstances of the status changes:

  • ACCOUNT_CLOSURE_REQUESTED: The customer has requested account closure.
  • COMPLIANCE_LOCKED: The account is suspended by compliance.
  • LIQUIDATED: The party is restricted to selling digital assets or withdrawing funds.
  • OTHER: Other unspecified reasons for suspension.
  • CLOSED: Confirmation that the account has been closed.
  • ENROLLMENT: Indicates the account is successfully created and actively enrolled.
  • RISK_KYC: Notification of a failed CIP check.
  • RISK_OFAC: Notification of a failed OFAC screening.
  • PENDING_REVIEW: The account is awaiting a manual compliance review.
  • DOCUMENT_VERIFICATION: Additional documentation is required for identity verification.

Account Creation Webhook Examples

Example 1: Active Account (ENROLLMENT)

{
  "requestId": "3f2b3e62-f26c-442d-8d4b-d029d5e9de50",
  "partnerPartyRef": "ABCDEFG123",
  "partnerWebhookType": "PARTY_EVENT",
  "created": "2023-07-14T17:26:42.53873Z",
  "partyStatus": "ACTIVE",
  "reasonCode": "ENROLLMENT",
  "verificationURL": "",
  "verificationURLExpiry": ""
}

Example 2: Failed KYC

{
  "requestId": "3f2b3e62-f26c-442d-8d4b-d029d5e9de50",
  "partnerPartyRef": "ABCDEFG123",
  "partnerWebhookType": "PARTY_EVENT",
  "created": "2023-07-14T17:26:42.53873Z",
  "partyStatus": "INACTIVE",
  "reasonCode": "RISK_KYC",
  "verificationURL": "",
  "verificationURLExpiry": ""
}

Example 3: Document Verification Required

{
  "requestId": "3f2b3e62-f26c-442d-8d4b-d029d5e9de50",
  "partnerPartyRef": "ABCDEFG123",
  "partnerWebhookType": "PARTY_EVENT",
  "created": "2023-07-14T17:26:42.53873Z",
  "partyStatus": "INACTIVE",
  "reasonCode": "DOCUMENT_VERIFICATION",
  "verificationURL": "https://verify-v2.socure.com/#/t/99f2e00e-3ffd-4c58-8e40-3d6f7934c426",
  "verificationURLExpiry": "2024-02-20T18:58:49.197534912Z"
}

Client Integration Testing Workflow

ScenarioTesting GuidelinesExpected Webhook Event Details
Account has passed KYC screeningEnroll a user with any PII information (aside from below scenarios)."partyStatus": "ACTIVE" "reasonCode": "ENROLLMENT"
Account has failed KYC screening on its initial attemptEnroll a user with the specified test PII values. See Bakkt documentation for details."partyStatus": "INACTIVE" OR "partyStatus": "SUSPENDED" "reasonCode": "RISK_KYC"
Account has failed KYC and has triggered document verificationEnroll a user with the specified test PII values. See Bakkt documentation for details."partyStatus": "INACTIVE" OR "partyStatus": "SUSPENDED" "reasonCode": "DOCUMENT_VERIFICATION" "verificationURL": "https://www.documentverification.sample.kyc.com"
Account has failed KYC and entered manual review queueEnroll a user with test date of birth 1961-01-01."partyStatus": "INACTIVE" OR "partyStatus": "SUSPENDED" "reasonCode": "PENDING_REVIEW"
Account has failed OFAC screeningEnroll a user with test email [email protected]."partyStatus": "INACTIVE" OR "partyStatus": "SUSPENDED" "reasonCode": "RISK_OFAC"

Fiat Onramp Webhook Events

Bakkt will call the webhook provided during onboarding by the Partner, whenever there is a transaction event with the following details.

  • partnerPartyRef Partner's identifier to a party on their side expected to be unique at the Partner
  • webhookType Type of webhook notification (PARTY_EVENT, BANK_ACCOUNT_LINK, FIAT_EVENT)
  • partyStatus Status of the consumer fiat account (ACTIVE, INACTIVE, SUSPENDED, LIQUIDATED, UNDER_REVIEW, OTP_REQUIRED, CLOSED, OPEN)
  • reasonCode Reason code for the change in status
  • amount The transaction amount
  • bankName Name of the Bank
  • accountLastFour Last 4 digits of the bank account
  • accountType Type of bank account
  • linkStatus Status of the linked account
  • created Date and time the transaction was completed
  • currency Type of currency (USD)
  • transactionId Unique Transaction ID generated by Bakkt
  • partnerTransactionRef Unique Transaction ID specified by partner to a transaction request
  • fiatTransactType Type of transaction (DEPOSIT, WITHDRAW, DEBIT, CREDIT, WITHDRAW_RETURN, DEPOSIT_RETURN, ACH_RETURN_FEE)
  • instrumentType Type of money movement instrument (ACH, WIRE, TPJ, DEBIT_CARD, PARTNER_WRITE_OFF, BAKKT_WRITE_OFF, DECEASED_PARTY, MANUAL_ADJUSTMENT)
  • transactionStatus Status of the transaction (PENDING, COMPLETED, DECLINED, CANCELLED)
  • verificationType Status of the verification (MANUAL, INSTANT, SAME_DAY_MICRODEPOSIT, AUTOMATED_MICRODEPOSIT)
  • declineReason Json object that contains type and description fields
    • type Response code for decline
    • description High level description of the decline reason

Webhook Example Events

Webhooks support multiple event types including party events, bank link events, and fiat events (ACH, Wire, TPJ, Manual Adjustments).

Party Event Example

{
  "requestId": "3f2b3e62-f26c-442d-8d4b-d029d5e9de50",
  "partnerPartyRef": "e8596570-f058-493b-a667-965b6ea2eb8e",
  "partnerWebhookType": "PARTY_EVENT",
  "created": "2023-07-14T17:26:42.53873Z",
  "partyStatus": "SUSPENDED",
  "reasonCode": "MAX_ACH_RETURNS"
}

Bank Account Link Example (Pending)

{
  "requestId": "ca5d9540-1f09-44d8-921b-aeca5caf16c9",
  "partnerPartyRef": "51275f6e-7111-402d-bf98-9a26d60072a61080p",
  "partnerWebhookType": "BANK_ACCOUNT_LINK",
  "created": "2023-08-08T19:12:53.034610562Z",
  "accountLinkingInfo": {
    "bankName": "N/A",
    "accountLastFour": "0000",
    "accountLinkStatus": "PENDING",
    "verificationType": "SAME_DAY_MICRODEPOSIT"
  }
}

ACH Deposit Examples

ACH Deposit Pending

{
  "requestId": "ba5d8540-6n78-44d8-921b-aeca5caf16g8",
  "partnerPartyRef": "53b16517-7766-4124-ab53-a798c310c139",
  "partnerWebhookType": "FIAT_EVENT",
  "fiatEvent": {
    "amount": 100,
    "created": "2023-05-11T18:32:24.448422Z",
    "currency": "USD",
    "transactionId": "5167d1e5-1ff9-49bd-86a2-38d8116c7d67",
    "partnerTransactionRef": "53b16517-7766-4124-ab53-a798c310c139",
    "fiatTransactType": "DEPOSIT",
    "instrumentType": "ACH",
    "transactionStatus": "PENDING"
  }
}

ACH Deposit Completed

{
  "requestId": "ch9d9540-1f09-44d8-876c-aeca5caf16g8",
  "partnerPartyRef": "53b16517-7766-4124-ab53-a798c310c139",
  "partnerWebhookType": "FIAT_EVENT",
  "fiatEvent": {
    "amount": 100,
    "created": "2023-05-13T18:32:24.448422Z",
    "currency": "USD",
    "transactionId": "4177d1e5-1ff9-4495-86a2-38d8116c7d99",
    "partnerTransactionRef": "77c16517-4513-4124-ab53-a798c310c848",
    "fiatTransactType": "DEPOSIT",
    "instrumentType": "ACH",
    "transactionStatus": "COMPLETED"
  }
}

Event Types

See the SQS section above for comprehensive details on event types and sample payloads for:

  • Order Entry Events (in-progress, filled, rejected, complete but no executions)
  • Transfer Events (accepted withdrawal, rejected withdrawal, in-progress deposit, complete deposit, complete withdrawal)
  • Gifting Event Examples (in progress gift, successful gift, rejected gift)
  • Single Allocation Event Examples (in progress allocation, complete allocation, rejected allocation)
  • Batch Event Examples (batch open event, batch close event, batch allocation event, batch rejected event)
  • Currency Batch Event Examples
  • Manual Adjustment Event Examples (deposit manual adjustment, withdrawal manual adjustment)