> ## Documentation Index
> Fetch the complete documentation index at: https://docs.smartcomply.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Webhooks

> Learn how to receive and verify webhook notifications from Adhere.

<Info>
  **In a nutshell:** Webhooks allow you to set up a notification system that can be used to receive updates on certain requests made to the Adhere API.
</Info>

Adhere uses webhooks to notify your application when specific events occur. This allows you to build automated workflows and integrate Adhere closely with your system.

## Why use Webhooks?

Generally, when you make a request to an API endpoint, you expect to get a near-immediate response. However, some requests may take a long time to process. In order to prevent a timeout error, a pending response is returned. Since your records need to be updated with the final state of the request, you need to either:

1. **Polling**: Make a request for an update at regular intervals.
2. **Webhooks**: Listen to events by using a webhook URL.

<Tip>
  We recommend using webhooks over polling. Webhooks are more efficient, reduce network overhead, and ensure your system is updated immediately when an event occurs.
</Tip>

## Setup & Integration

To start receiving webhook notifications, follow these steps to configure your environment:

<Steps>
  <Step title="Configure Webhook URL">
    Provide the endpoint on your server where Adhere will send `POST` requests. This should be a publicly accessible URL.
  </Step>

  <Step title="Set Hash Key">
    A secret key used to sign the webhook payload. You must keep this secure and use it to verify that requests are coming from Adhere.
  </Step>

  <Step title="Save Configuration">
    Save your settings in the Adhere dashboard under the **Integrations** section.
  </Step>
</Steps>

## Security & Verification

All webhook requests from Adhere include a `X-Hub-Signature` header. This header contains the HMAC SHA256 signature of the request body, signed with your Hash Key.

### Verifying Signatures

To ensure that a webhook request is genuinely from Adhere, you should verify the signature before processing the payload.

<CodeGroup>
  ```python Python theme={null}
  import hmac
  import hashlib
  import base64
  from flask import request, abort

  def verify_webhook(request, hash_key):
      sig_header = request.headers.get("X-Hub-Signature")
      if not sig_header:
          abort(401, "Missing signature")

      # The header format is "sha256=<signature>"
      _, _, received_sig = sig_header.partition("=")

      # Compute the HMAC SHA256 digest of the raw request body
      raw_body = request.get_data()
      digest = hmac.new(
          hash_key.encode("utf-8"),
          raw_body,
          hashlib.sha256
      ).digest()

      expected_sig = base64.b64encode(digest).decode("utf-8")

      if not hmac.compare_digest(received_sig, expected_sig):
          abort(401, "Invalid signature")
  ```

  ```javascript Node.js theme={null}
  const crypto = require('crypto');

  function verifyWebhook(req, hashKey) {
    const signature = req.headers['x-hub-signature'];
    if (!signature) {
      throw new Error('Missing signature');
    }

    const [algo, receivedSig] = signature.split('=');
    const hmac = crypto.createHmac('sha256', hashKey);
    const digest = hmac.update(req.rawBody).digest('base64');

    if (receivedSig !== digest) {
      throw new Error('Invalid signature');
    }
  }
  ```

  ```php PHP theme={null}
  function verify_webhook($request_body, $hash_key, $received_sig) {
      $expected_sig = base64_encode(hash_hmac('sha256', $request_body, $hash_key, true));

      if (!hash_equals($received_sig, $expected_sig)) {
          http_response_code(401);
          exit("Invalid signature");
      }
  }
  ```

  ```go Go theme={null}
  import (
  	"crypto/hmac"
  	"crypto/sha256"
  	"encoding/base64"
  	"io/ioutil"
  	"net/http"
  	"strings"
  )

  func verifyWebhook(w http.ResponseWriter, r *http.Request, hashKey string) bool {
  	sigHeader := r.Header.Get("X-Hub-Signature")
  	if sigHeader == "" {
  		return false
  	}

  	// Header format is "sha256=<signature>"
  	parts := strings.Split(sigHeader, "=")
  	if len(parts) != 2 {
  		return false
  	}
  	receivedSig := parts[1]

  	body, _ := ioutil.ReadAll(r.Body)
  	h := hmac.New(sha256.New, []byte(hashKey))
  	h.Write(body)
  	expectedSig := base64.StdEncoding.EncodeToString(h.Sum(nil))

  	return hmac.Equal([]byte(receivedSig), []byte(expectedSig))
  }
  ```

  ```csharp .NET theme={null}
  using System.Security.Cryptography;
  using System.Text;

  public bool VerifyWebhook(string requestBody, string hashKey, string receivedSig)
  {
      var keyBytes = Encoding.UTF8.GetBytes(hashKey);
      var bodyBytes = Encoding.UTF8.GetBytes(requestBody);

      using (var hmac = new HMACSHA256(keyBytes))
      {
          var hashBytes = hmac.ComputeHash(bodyBytes);
          var expectedSig = Convert.ToBase64String(hashBytes);

          return expectedSig == receivedSig;
      }
  }
  ```
</CodeGroup>

<Warning>
  Always verify the `X-Hub-Signature` header to prevent unauthorized requests from interacting with your server.
</Warning>

## Event Payload Structure

All webhooks follow a consistent JSON structure:

| Parameter | Type    | Description                                                                         |
| :-------- | :------ | :---------------------------------------------------------------------------------- |
| `success` | boolean | Indicates if the event was processed successfully.                                  |
| `module`  | string  | The Adhere module that triggered the event (e.g., `transaction_monitoring`, `kyc`). |
| `event`   | string  | The specific event type.                                                            |
| `data`    | object  | The actual payload data (e.g., transaction details, KYC results).                   |

## Supported Events

### Transaction Monitoring

* **Module**: `transaction_monitoring`
* **Events**:
  * `suspicious_transaction`: Triggered when a transaction is processed and deemed suspicious.

    ```json theme={null}
    {
      "data": {
        "id": 23,
        "is_internal_blacklisted": false,
        "is_blacklisted_by": null,
        "case_id": "#8N2ZI6",
        "case_sla": "2025-08-23T09:39:31.551479Z",
        "case_status": "open",
        "transaction_id": "9201634916397893719",
        "amount": 12345.0,
        "currency": "EUR",
        "transaction_type": "card",
        "account_type": "individual",
        "customer_name": "David Seaman",
        "customer_email": "davidseaman@example.com",
        "customer_ip_address": "192.168.0.8",
        "customer_location": "Yaba, LG",
        "origin_account_no": "4321567809",
        "origin_bank_code": "327",
        "transaction_description": "Payment for order #78901",
        "destination_account_no": "0123456789",
        "destination_bank_code": "723",
        "merchant_name": null,
        "merchant_location": null,
        "status": "suspicious",
        "card_bin": null,
        "card_last4": null,
        "bvn": "7890123456",
        "fraud_percent": null,
        "tag": [
          "1 rule(s) triggered"
        ],
        "sender_blacklisted": false,
        "receiver_blacklisted": false,
        "rules_flagged": [
          "Any transaction by an individual that exceeds an amount {a}"
        ],
        "additional_info": {},
        "date_created": "2025-08-20T09:39:31.425545Z",
        "date_updated": "2026-01-16T13:46:07.388489Z",
        "branch": 2
      },
      "event": "suspicious_transaction",
      "module": "transaction_monitoring",
      "success": true
    }
    ```

## Testing Locally

Before deploying to production, we recommend testing your webhook implementation locally.

1. **Use ngrok**: Use [ngrok](https://ngrok.com/) to create a secure tunnel to your local server.
2. **Set Webhook URL**: Update your Adhere dashboard with the ngrok URL (e.g., `https://your-subdomain.ngrok-free.app/webhooks`).
3. **Inspect Requests**: Use the ngrok dashboard or [Webhook.site](https://webhook.site/) to inspect the payloads and headers sent by Adhere.

## Best Practices

* **Acknowledge Immediately**: Your server should return a `200 OK` response as quickly as possible. Heavy processing should be handled asynchronously using a task queue.
* **Handle Retries**: Adhere will retry failed webhook deliveries (non-2xx responses) up to 3 times with an exponential backoff.
* **Use Idempotency**: Ensure your system can handle the same webhook multiple times safely. Use the event's unique ID to track processed events.

<Note>
  If your server does not return a 2xx response, Adhere will consider the delivery as failed and will attempt to retry.
</Note>
