# Webhooks Guide

## Overview

ComplyCube uses webhooks to notify your application when an event happens in your account. Webhooks are particularly useful for asynchronous events like when a check has concluded.

Not all ComplyCube integrations require webhooks. Keep reading to learn more about webhooks and when you should use them.

## Introduction to webhooks <a href="#what-are-webhooks" id="what-are-webhooks"></a>

Webhooks are a combination of elements that collectively create a notification and reaction system within an integration.

Metaphorically, webhooks are like a phone number that ComplyCube calls to notify you of your account activity. The activity could be that a document check is complete. The webhook endpoint is the person answering that call who takes actions based on the specific information it receives.

Non-metaphorically, the webhook endpoint is just more code on your server. The webhook endpoint has an associated URL (e.g., **<https://example.com/webhooks>**). The ComplyCube notifications are [Event](https://docs.complycube.com/api-reference/other-resources/webhooks#the-event-object) objects. This object contains all the relevant information about what just happened, including the type of event and the data associated with that event. The webhook endpoint uses the event details to take required actions, such as putting a temporary hold on a customer's account or transaction.

## Webhook components <a href="#when-to-use-webhooks" id="when-to-use-webhooks"></a>

ComplyCube Webhooks integration includes the following:

* **Events**. An action or change in data that generates notifications. Webhooks can be used to create alerts that trigger these events. Please look at the [Webhooks API page](https://docs.complycube.com/api-reference/other-resources/webhooks) for the list of supported event types.
* **Subscriptions**. Configured in the developer portal or via API to subscribe to notifications associated with a specific event type.
* **Notification URL**. The configurable service in the application to which alerts are sent.
* **Notification body**. Details about the object associated with the event.

## When to use <a href="#when-to-use-webhooks" id="when-to-use-webhooks"></a>

Many events that occur within the ComplyCube account have synchronous results – immediate and direct – to an executed request. For example, a successful request to [create a client](https://app.gitbook.com/s/kAhgmUKSf8CFUFVL3GEe/core-resources/clients/create-a-client) immediately returns a `client` object. Such requests don't require webhooks, as the key information is already available.

On the other hand, [Checks](https://app.gitbook.com/s/kAhgmUKSf8CFUFVL3GEe/core-resources/checks) are asynchronous: happening later and not directly in response to your code's execution. With these events, ComplyCube needs to notify your integration about changes to an object's status so your integration can take subsequent steps.

The specific actions of your webhook endpoint differ based on the event. Some examples include:

* Based on a check outcome, decide to accept or reject a customer's request to be on-boarded onto your platform.
* Perform follow-up actions upon getting alerted by our real-time continuous monitoring engine that a client's status has changed.

## Signature verification

### Using official SDKs

ComplyCube signs the webhook events it sends to your endpoints by including a signature in each event's `ComplyCube-Signature` header. This lets you verify that the events were sent by ComplyCube, not by a third party. You can verify signatures using our official libraries or manually using your own solution.

Use one of our official libraries to verify signatures. You perform the verification by providing the event payload, the `ComplyCube-Signature` header, and the endpoint's secret. If verification fails, ComplyCube returns an error.

{% tabs %}
{% tab title="Node.js" %}

```javascript
const { EventVerifier } = require('@complycube/api')

// Provide your webhook secret to the EventVerifier 
const webhookSecret = process.env.COMPLYCUBE_WEBHOOK_SECRET;
const eventVerifier = new EventVerifier(webhookSecret);

// This example uses Express to receive webhooks
const app = require('express')();

// Use body-parser to retrieve the raw body as a buffer
const bodyParser = require('body-parser');

// Match the raw body to content type application/json
app.post('/webhook', bodyParser.json(), (request, response) => {
  const signature = request.headers['complycube-signature'];

  let event;

  try {
    event = eventVerifier.constructEvent(
      JSON.stringify(request.body),
      signature
    );
  }
  catch (err) {
    response.status(400).send(`Webhook Error: ${err.message}`);
  }

  // Handle the event
  switch (event.type) {
    case 'check.completed': {
      const checkId = event.payload.id;
      const checkOutCome = event.payload.outcome;
      console.log(`Check ${checkId} completed with outcome ${checkOutCome}`);
      break;
    }
    case 'check.pending': {
      const checkId = event.payload.id;
      console.log(`Check ${checkId} is pending`);
      break;
    }
    // ... handle other event types
    default: {
      // Unexpected event type
      return response.status(400).end();
    }
  }

  // Return a response to acknowledge receipt of the event
  response.json({received: true});
});

app.listen(4242, () => console.log('Running on port 4242'));
```

{% endtab %}

{% tab title="Python" %}

```python
import json
import os
import flask
from flask import request, jsonify
import complycube.eventverifier as ccevnt

# Sample flask app
app = flask.Flask(__name__)

# Provide your webhook secret to the EventVerifier
ev = ccevnt.EventVerifier(os.getenv('COMPLYCUBE_WEBHOOK_SECRET'))

@app.route('/webhook', methods=['POST'])
def webhook():
    if 'Complycube-Signature' not in request.headers:
        return jsonify({'received':'False'}), 400
    
    signature = request.headers['complycube-signature']
    try:
        event = ev.construct_event(request.get_data(),signature)
        check_id = event.payload.id
        if event.type == 'check.completed':
            check_outcome = event.payload.outcome
            print('Check %s completed with outcome %s' % 
                  (check_id, check_outcome))
        elif event.type == 'check.pending':
            print('Check %s is pending' % check_id)
        # ... Handle other expected events
        else:
            return jsonify({'received':'False'}), 400      
    except VerificationError:
        return jsonify({'received':'False'}), 400 
    
    return jsonify({'received':'True'}), 200

app.run()
```

{% endtab %}

{% tab title="PHP" %}

```php
use ComplyCube\ComplyCubeClient;
use ComplyCube\Model\Event;
use ComplyCube\EventVerifier;
use ComplyCube\Exception\VerificationException;

header("Access-Control-Allow-Origin: *");
header("Content-Type: application/json; charset=UTF-8");
header("Access-Control-Allow-Methods: POST");
header("Access-Control-Max-Age: 3600");
header("Access-Control-Allow-Headers: Content-Type, Authorization, Complycube-Signature, X-Requested-With");
define("SIGNATURE_KEY", 'complycube-signature');

$data = file_get_contents('php://input');
$verifier = new \ComplyCube\EventVerifier('WEBHOOK_SECRET');
$headers = apache_request_headers();

try {
    if (!isset($headers[SIGNATURE_KEY])) {
        http_response_code(400);
        return;
    }
    $event = $verifier->constructEvent($data, $headers[SIGNATURE_KEY]);
    switch ($event->type) {
        case "check.completed":
            $outcome = $event->payload->outcome;
            # do completed check processing
            break;
        case "check.pending":
            # do pending check processing
            break;
        default:
            http_response_code(400);
            return;
    }
    http_response_code(200);
    return;
} catch (\ComplyCube\Exception\VerificationException $e) {
    http_response_code(400);
    return;
}
```

{% endtab %}

{% tab title=".NET" %}

```csharp
using System.Text;
using System.Text.Json;
using ComplyCube.Net;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Primitives;

namespace CCSimpleDotNetSample.Controllers {
 [ApiController]
 [Route("[controller]")]
 public class SimpleEventListenerController: ControllerBase {
  [HttpPost]
  public async Task < IActionResult > Post() {
   var re = Request;
   StringValues payloadSignature;
   re.Headers.TryGetValue("complycube-signature", out payloadSignature);

   // Replace WEBHOOK_SECRET with the secret from your dev console
   var evr = new EventVerifier("WEBHOOK_SECRET");

   // Use StreamReader to read the entire body of the event
   using(var reader = new StreamReader(Request.Body, Encoding.UTF8)) {
    var payloadText = await reader.ReadToEndAsync();
    
    if (string.IsNullOrWhiteSpace(payloadText)){
     return StatusCode(400);
    }
    
    try {
     // Build the Event object and confirm its signature
     var receivedEvent = evr.ConstructEvent(payloadText, payloadSignature);
     if (receivedEvent != null) {
      var payloadObject = JsonSerializer.Deserialize<Dictionary<string, object>>(
       payloadText
      );
      
      // process the payload and action accordingly
      if (payloadObject.TryGetValue("type", out var eventTypeObj) &&
       payloadObject.TryGetValue("payload", out var eventPayloadObj)) {
       var eventType = eventTypeObj.ToString();
       var eventPayload = JsonSerializer.Serialize(eventPayloadObj);

       switch (eventType) {
        case "check.completed":
         if (eventPayloadObj is Dictionary < string, object > payloadDict &&
          payloadDict.TryGetValue("outcome", out var outcome)) {
           // Do completed check processing
          }
         break;
        case "check.pending": {
          // Do pending check processing
         }
         break;
        default:
         return StatusCode(400);
       }
       return StatusCode(200, receivedEvent);
      }
     }
    } catch (ComplyCube.Net.Exceptions.VerificationException) {
     return StatusCode(400);
    }
    return StatusCode(400);
   }
  }
 }
}
```

{% endtab %}
{% endtabs %}

### Verify manually

ComplyCube generates signatures using a hash-based message authentication code ([HMAC](https://en.wikipedia.org/wiki/Hash-based_message_authentication_code)) with [SHA-256](https://en.wikipedia.org/wiki/SHA-2). Although using our official libraries to verify webhook event signatures is recommended, you can create a custom solution by following these steps.

1. Extract the `complycube-signature` from the HTTP headers.
2. Determine the expected signature by computing an HMAC with the SHA256 hash function. Use your webhook's secret as the key, and use the `request body` string as the message.
3. Compare the signature in the header to the expected signature.

### Best Practices <a href="#what-are-webhooks" id="what-are-webhooks"></a>

### Event types

You should configure your webhook endpoints to receive only the types of events required by your integration. Listening to extra events (or all events) will put undue strain on your server and is not recommended.

You can [change the events](https://app.gitbook.com/s/kAhgmUKSf8CFUFVL3GEe/other-resources/webhooks#event-types) a webhook endpoint will receive in the Dashboard or the API.

### Handle duplicate events <a href="#duplicate-events" id="duplicate-events"></a>

Webhook endpoints might occasionally receive the same event more than once. We advise you to guard against duplicated event receipts by making your event processing [idempotent](https://en.wikipedia.org/wiki/Idempotence). One way of doing this is by logging the events you've processed and not processing already-logged events.

### Order of events

ComplyCube does not guarantee the delivery of events in the order they are generated. Your endpoint should not expect delivery of events in a given order and should handle this accordingly. You can also use the API to fetch any missing objects.

### Webhooks over HTTPS

If you use an HTTPS URL for your webhook endpoint, ComplyCube will validate that your server's connection is secure before sending your webhook data. For this to work, you must correctly configure your server to support HTTPS with a valid server certificate.\ <br>
