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

# Webhooks

>  Everything you need to know on how to monitor events when specific actions take place in your integration.

## Why use webhook

When building Simpu integrations, you might want your applications to receive events as they occur, so that your backend systems can execute actions accordingly.

To enable webhook events, you need to register webhook endpoints. After registering them, Simpu can push real-time event data to your application's webhook endpoint when specific events occur. Simpu uses HTTPS to send webhook events to your app as a JSON payload, which includes detailed information about the event.
Receiving webhook events is particularly useful for tracking actions.

## Event Overview

Simpu generates event data that can be sent to you to inform you of activities in your account.

When an event occurs, Simpu creates a new Event object. A single action might trigger multiple events.

By registering webhook endpoints in your Simpu account, you allow Simpu to automatically send Event objects via POST requests to your application's registered webhook endpoint. Once your webhook endpoint receives the Event, your app can execute backend actions.

Webhooks allows you to listen to real-time events happening across your Simpu workspace. With webhooks, you can build custom integrations with simpu.

## Verifying Events

It's crucial to verify signatures of incoming webhook events to ensure they are genuine and haven't been tampered with. This verification process helps protect your application from processing fraudulent events.
For security purposes, each webhook event comes with a signature in the `x-simpu-webhook-signature` header. This signature is created using the following process:

The event payload is signed using `HMAC-SHA512` algorithm
The result is encoded in `Base64` format
This encoded signature is sent in the header

To verify an event:

Take the raw event payload (as a string)
Take the signature from the `x-simpu-webhook-signature header`
Use your secret key to compute a new signature
Compare the computed signature with the received signature

Here's a quick example of the verification flow:

```javascript theme={null}
// Get the signature from headers
const signature = request.headers['x-simpu-webhook-signature'];

// Get the raw payload
const payload = JSON.stringify(request.body);

// Verify the signature
const isValid = verifySignature(payload, signature, secretKey);

if (!isValid) {
    // Reject the webhook
    return response.status(401).send('Invalid signature');
}

// Process the webhook
// ... your processing logic here
```

Only process webhook events after successful signature verification. This ensures that your application only responds to genuine events from the authorized source.

<CodeGroup>
  ```javascript Javascript theme={null}
  const crypto = require('crypto');

  /**
   * Verify if the provided signature matches the computed signature for the data
   *
   * @param {string} data - The original data that was signed
   * @param {string} signature - The Base64 encoded signature to verify
   * @param {string} secretKey - The secret key used for signing
   * @returns {boolean} Returns true if signatures match, false otherwise
   */
  function verifySignature(data, signature, secretKey) {
      try {
          // Create HMAC SHA512 hash
          const hmac = crypto.createHmac('sha512', secretKey);

          // Update with the data
          hmac.update(data);

          // Generate computed signature in base64
          const computedSignature = hmac.digest('base64');

          // Use crypto.timingSafeEqual to prevent timing attacks
          // First, convert both signatures to buffers
          const signatureBuffer = Buffer.from(signature);
          const computedSignatureBuffer = Buffer.from(computedSignature);

          // Compare signatures using constant-time comparison
          return signatureBuffer.length === computedSignatureBuffer.length &&
              crypto.timingSafeEqual(signatureBuffer, computedSignatureBuffer);

      } catch (error) {
          console.error('Error verifying signature:', error);
          return false;
      }
  }
  ```

  ```python Python theme={null}
  import hmac
  import hashlib
  import base64

  def verify_signature(data: str, signature: str, secret_key: str) -> bool:
      """
      Verify if the provided signature matches the computed signature for the data

      Args:
      data: The original data that was signed
      signature: The Base64 encoded signature to verify
      secret_key: The secret key used for signing

      Returns:
      bool: True if signatures match, False otherwise
      """
      try:
          # Convert secret to bytes using UTF-8 encoding
          secret_bytes = secret_key.encode('utf-8')

          # Convert data to bytes
          data_bytes = data.encode('utf-8')

          # Create a new HMAC using the secret key
          hmac_obj = hmac.new(secret_bytes, msg=data_bytes, digestmod=hashlib.sha512)

          # Generate the signature
          computed_signature = base64.b64encode(hmac_obj.digest())

          # Decode received signature from base64
          received_signature_bytes = base64.b64decode(signature)

          # Compare signatures using constant time comparison
          # This prevents timing attacks
          return hmac.compare_digest(computed_signature, signature.encode('utf-8'))

      except Exception as e:
          print(f"Error verifying signature: {str(e)}")
          return False
  ```

  ```php PHP theme={null}
  <?php
  class SignatureVerifier {
      /**
       * Verify if the provided signature matches the computed signature for the data
       *
       * @param string $data The original data that was signed
       * @param string $signature The Base64 encoded signature to verify
       * @param string $secretKey The secret key used for signing
       * @return bool Returns true if signatures match, false otherwise
       */
      public static function verifySignature(string $data, string $signature, string $secretKey): bool
      {
          try {
              // Generate HMAC SHA512 hash
              $computedSignature = base64_encode(
                  hash_hmac(
                      'sha512',
                      $data,
                      $secretKey,
                      true // Set to true to get raw binary output
                  )
              );

              // Use hash_equals for constant-time string comparison
              // This prevents timing attacks
              return hash_equals($computedSignature, $signature);

          } catch (Exception $e) {
              error_log("Error verifying signature: " . $e->getMessage());
              return false;
          }
      }
  }
  ?>
  ```

  ```java Java theme={null}
  public static boolean verifySignature(String data, String signature, String secretKey) {
    try {
        // Convert secret to bytes using UTF-8 encoding
        byte[] secretBytes = secretKey.getBytes(StandardCharsets.UTF_8);

        // Create HMAC-SHA512 instance
        Mac hmacSHA512 = Mac.getInstance("HmacSHA512");
        SecretKeySpec secretKeySpec = new SecretKeySpec(secretBytes, "HmacSHA512");
        hmacSHA512.init(secretKeySpec);

        // Generate signature for the data
        byte[] dataBytes = data.getBytes(StandardCharsets.UTF_8);
        byte[] computedSignatureBytes = hmacSHA512.doFinal(dataBytes);
        String computedSignature = Base64.getEncoder().encodeToString(computedSignatureBytes);

        // Compare signatures using constant-time comparison
        return MessageDigest.isEqual(
            computedSignature.getBytes(StandardCharsets.UTF_8),
            signature.getBytes(StandardCharsets.UTF_8)
        );

    } catch (NoSuchAlgorithmException | InvalidKeyException e) {
        logger.error("Error verifying signature: ", e);
        return false;
    }
  }
  ```
</CodeGroup>

## Responding to an event

To handle Simpu webhooks, your application should respond with a `200 OK` status to acknowledge the receipt of the event. If the response is anything other than `200`, `201`, or `202`, the event will be considered unacknowledged, and Simpu will retry sending it every hour for up to 24 hours. Only the status code is required—any other parameters or request bodies will be ignored.

If your application needs to execute long-running tasks after receiving an event, it should still respond with a `200 OK` immediately, before proceeding with the task, to avoid duplicate events being triggered.

## Types of events

Here are the events we currently support. More will be added to this list as we integrate additional actions in the future.

| Event               | Description                                                                               |
| :------------------ | :---------------------------------------------------------------------------------------- |
| `airtime.success`   | Airtime transaction was successfully completed                                            |
| `airtime.failed`    | Airtime transaction attempted failed                                                      |
| `airtime.duplicate` | If you attempt to send the same airtime amount to the same recipient at the same interval |
| `airtime.error`     | Unknown error                                                                             |
| `text.delivered`    | The SMS message was successfully delivered                                                |
| `text.undelivered`  | The SMS message failed to deliver                                                         |
| `text.clicked`      | The link in the SMS was clicked                                                           |
| `email.delivered`   | The email was successfully delivered to the recipient                                     |
| `email.bounced`     | The email failed to deliver                                                               |
| `email.opened`      | The recipient opened the email                                                            |
| `email.clicked`     | The recipient clicked a link in the email                                                 |

## Example

```json theme={null}
{
  "data": {
    "object": {
      "callback": "https://webhook.site/70652a3f-c84f-4efe-a63d-f272e66abc29",
      "amount": "12.0",
      "object": "airtime",
      "organisation_id": "897436867c0911eb8e5d9a88eb4ca9b7",
      "created_datetime": "2024-09-23T20:19:59.873639",
      "network": "GLO",
      "recipient": "2349055019322",
      "status": "success"
    }
  },
  "id": "evt.airtime_ExTefBPVsc1F901NXSNm1EZnvzpfOTcQ",
  "object": "event",
  "type": "AIRTIME_SUCCESS",
  "timestamp": "Sep 23, 2024, 8:20:01 PM"
}
```
