# Webhooks

{% hint style="info" %}
Webhooks provide a convenient way to establish a notification system for receiving updates on specific requests made to the Lahza API.
{% endhint %}

### Introduction <a href="#introduction" id="introduction"></a>

Typically, when making a request to an API endpoint, a prompt response is anticipated. However, certain requests may require a longer processing time, potentially resulting in timeout errors. To mitigate such errors, an interim response known as a pending response is returned. To obtain the final status of the request and update your records accordingly, you have two options:

1. Polling: You can periodically make requests to check for updates on the request by polling the API endpoint. or,
2. Webhooks: Alternatively, you can set up a webhook URL to listen for events. This way, you will receive automatic notifications whenever there is an update on the request, eliminating the need for continuous polling.

{% hint style="info" %}
Choose webhooks over callbacks or polling for better control and reliability. Callbacks can fail due to network issues or device shutdown, while webhooks provide more consistent and efficient notifications.
{% endhint %}

### Polling vs Webhooks <a href="#polling-vs-webhooks" id="polling-vs-webhooks"></a>

[<br>](https://paystack.com/docs/static/4e48ac469a56d3f950460a6f7203b0fe/4352a/polling_webhooks.png)To obtain the final status of a request through polling, you need to regularly send GET requests at specific intervals. For instance, when a customer makes a payment for a transaction, you would continuously request the transaction status until it becomes successful.

On the other hand, webhooks enable the resource server (such as Lahza) to send updates to your server whenever there is a change in the request status. These status changes are referred to as events, which you can conveniently listen to on a designated POST endpoint known as your webhook URL.

The table below summarizes the disparities between polling and webhooks:<br>

|             | Polling                                                    | Webhooks                                                          |
| ----------- | ---------------------------------------------------------- | ----------------------------------------------------------------- |
| Mechanism   | Periodically sending GET requests for status updates       | Receiving automatic updates from the resource server              |
| Workflow    | Continuously checking for updates                          | Listening for events through a webhook URL                        |
| Efficiency  | Requires frequent requests, resulting in higher API usage  | Reduces API usage as updates are pushed from the resource server  |
| Real-time   | Updates may not be real-time due to polling intervals      | Real-time updates as they are pushed from the resource server     |
| Reliability | Relies on successful API requests and network connectivity | More reliable as updates are directly sent by the resource server |

### Create a webhook URL <a href="#create-a-webhook-url" id="create-a-webhook-url"></a>

\
A webhook URL is essentially a POST endpoint where a resource server sends updates. This URL should be capable of parsing a JSON request and responding with a 200 OK status code.

{% tabs %}
{% tab title="Node.JS" %}
{% code lineNumbers="true" %}

```javascript
const express = require('express');
const app = express();

app.use(express.json());

app.post('/webhook', (req, res) => {
  const data = req.body;
  // Process the data or perform desired actions
  // ...
  res.sendStatus(200);
});

app.listen(3000, () => {
  console.log('Webhook server is running on port 3000');
});
```

{% endcode %}
{% endtab %}

{% tab title="PHP" %}
{% code lineNumbers="true" %}

```php
$data = json_decode(file_get_contents('php://input'), true);
// Process the data or perform desired actions
// ...

http_response_code(200);
```

{% endcode %}
{% endtab %}

{% tab title="Python" %}
{% code lineNumbers="true" %}

```python
from flask import Flask, request

app = Flask(__name__)

@app.route('/webhook', methods=['POST'])
def handle_webhook():
    data = request.get_json()
    # Process the data or perform desired actions
    # ...
    return '200 OK'

if __name__ == '__main__':
    app.run()
```

{% endcode %}
{% endtab %}
{% endtabs %}

Once your webhook URL receives an event, it must parse and acknowledge the event by returning a 200 OK status in the HTTP header. If the response header does not contain a 200 OK status, we will continue sending events for the next 72 hours.

* In live mode, webhooks are initially sent every 3 minutes for the first 4 attempts. After that, the frequency switches to hourly for the next 72 hours.
* In test mode, webhooks are sent hourly for the next 72 hours.

{% hint style="warning" %}
To ensure proper handling of long-running tasks within your webhook function, it is essential to acknowledge the event before executing those tasks. Failure to do so may result in a request timeout and an automatic error response from your server. Remember that a 200 OK response is crucial to avoid retries, as described in the previous paragraph.
{% endhint %}

### Verify event origin <a href="#verify-event-origin" id="verify-event-origin"></a>

\
To maintain the security and integrity of your webhook URL, it is crucial to verify that the events originate from Lahza rather than from malicious actors. You can employ two methods to ensure the authenticity of events sent to your webhook URL:

1. Signature Validation
2. IP Whitelisting

#### Signature validation <a href="#signature-validation" id="signature-validation"></a>

Lahza sends events with the `x-lahza-signature` header, which contains a HMAC SHA256 signature of the event payload. Before processing the event, it is important to verify this header signature to ensure the integrity of the payload.

{% tabs %}
{% tab title="NodeJS" %}

```javascript
const crypto = require('crypto');
const secret = process.env.SECRET_KEY;

app.post("/my/webhook/url", (req, res) => {
  // Validate event
  const hash = crypto.createHmac('sha256', secret).update(req.body).digest('hex');
  if (hash === req.headers['x-lahza-signature']) {
    // Retrieve the request's body
    const event = req.body;
    // Do something with the event
    // ...
  }
  res.sendStatus(200);
});
```

{% endtab %}

{% tab title="PHP" %}
{% code lineNumbers="true" %}

```php
$secretKey = 'YOUR_SECRET_KEY';

// Retrieve the payload and signature from the request
$payload = file_get_contents('php://input');
$signature = $_SERVER['HTTP_X_LAHZA_SIGNATURE'];

// Verify signature
$hash = hash_hmac('sha256', $payload, $secretKey);
if ($hash === $signature) {
    // Signature is valid, process the event
    $event = json_decode($payload, true);
    // Do something with the event
    // ...
}

http_response_code(200);

```

{% endcode %}
{% endtab %}

{% tab title="C#" %}
{% code lineNumbers="true" %}

```csharp
using System;
using System.Security.Cryptography;
using System.Text;

public class WebhookVerification
{
    private const string SecretKey = "YOUR_SECRET_KEY";

    public static void Main(string[] args)
    {
        string payload = // Retrieve the payload from the request
        string signature = // Retrieve the signature from the request headers

        // Verify signature
        string hmacDigest = CalculateHmac(payload, SecretKey, "HMACSHA256");
        if (hmacDigest.Equals(signature))
        {
            // Signature is valid, process the event
            // Do something with the event
            // ...
        }
    }

    private static string CalculateHmac(string payload, string secretKey, string algorithm)
    {
        byte[] keyBytes = Encoding.UTF8.GetBytes(secretKey);
        byte[] payloadBytes = Encoding.UTF8.GetBytes(payload);

        using (HMACSHA512 hmac = new HMACSHA512(keyBytes))
        {
            byte[] hmacBytes = hmac.ComputeHash(payloadBytes);
            return BitConverter.ToString(hmacBytes).Replace("-", string.Empty).ToLower();
        }
    }
}

```

{% endcode %}
{% endtab %}

{% tab title="Java" %}
{% code lineNumbers="true" %}

```java
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import java.security.MessageDigest;
import java.util.Base64;
import java.nio.charset.StandardCharsets;

public class WebhookVerification {
    private static final String SECRET_KEY = "YOUR_SECRET_KEY";

    public static void main(String[] args) {
        String payload = // Retrieve the payload from the request
        String signature = // Retrieve the signature from the request headers

        // Verify signature
        String hmacDigest = calculateHmac(payload, SECRET_KEY, "HmacSHA256");
        if (hmacDigest.equals(signature)) {
            // Signature is valid, process the event
            // Do something with the event
            // ...
        }
    }

    private static String calculateHmac(String payload, String secretKey, String algorithm) {
        try {
            Mac mac = Mac.getInstance(algorithm);
            SecretKeySpec keySpec = new SecretKeySpec(secretKey.getBytes(StandardCharsets.UTF_8), algorithm);
            mac.init(keySpec);
            byte[] hmacBytes = mac.doFinal(payload.getBytes(StandardCharsets.UTF_8));
            return Base64.getEncoder().encodeToString(hmacBytes);
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
    }
}

```

{% endcode %}
{% endtab %}

{% tab title="Python" %}
{% code lineNumbers="true" %}

```python
import hashlib
import hmac
from flask import Flask, request

app = Flask(__name__)
secret_key = 'YOUR_SECRET_KEY'

@app.route('/my/webhook/url', methods=['POST'])
def handle_webhook():
    payload = request.get_data()
    signature = request.headers.get('x-lahza-signature')

    # Verify signature
    hmac_digest = hmac.new(secret_key.encode(), payload, hashlib.sha256).hexdigest()
    if hmac.compare_digest(hmac_digest, signature):
        # Signature is valid, process the event
        event = request.json
        # Do something with the event
        # ...

    return 'OK'

if __name__ == '__main__':
    app.run()
```

{% endcode %}
{% endtab %}
{% endtabs %}

#### IP whitelisting <a href="#ip-whitelisting" id="ip-whitelisting"></a>

\
To restrict access to your webhook URL and only allow requests from specific IP addresses, it is recommended to whitelist the following IP addresses for Lahza:

* `161.35.20.140`
* `209.38.219.189`

By whitelisting these IP addresses and blocking requests from other IP addresses, you can ensure that only legitimate requests from Lahza are accepted, while considering requests from other IP addresses as potentially unauthorized.

{% hint style="info" %}
**Whitelisting is Domain Independent:**

It's important to note that the IP addresses mentioned above are applicable to both the test and live environments. You can whitelist these IP addresses in both your staging and production environments, ensuring consistent and secure webhook handling across different stages of your application.
{% endhint %}

### Go live checklist <a href="#go-live-checklist" id="go-live-checklist"></a>

\
To ensure a smooth experience with your webhook URL, consider the following suggestions:

1. Add the webhook URL on your Lahza dashboard: Make sure to register and configure your webhook URL in your Lahza dashboard. This allows Lahza to send event updates to your specified URL.
2. Ensure your webhook URL is publicly available: It is important that your webhook URL is accessible from the internet. Localhost URLs cannot receive webhook events. Ensure that your webhook URL is publicly accessible for successful event delivery.
3. Trailing slash in .htaccess (if applicable): If you are using .htaccess to handle URL rewriting, remember to include a trailing slash (/) at the end of the URL to ensure proper routing.
4. Test your webhook: Before deploying your webhook in a production environment, test it to ensure that you receive the JSON body of the events and respond with a 200 OK HTTP status code. This confirms that your webhook is functioning correctly.
5. Handle long-running tasks: If your webhook function involves long-running tasks, it is recommended to acknowledge the webhook event by returning a 200 OK response before proceeding with those tasks. This prevents request timeouts and allows for efficient processing of events.
6. Failure handling: If Lahza does not receive a 200 OK HTTP response from your webhook, it will be considered a failed attempt. In live mode, failed attempts are retried every 3 minutes for the first 4 tries. After that, the retry interval switches to hourly for the next 72 hours. Similarly, in test mode, failed attempts are retried hourly for the next 72 hours.

By following these guidelines, you can ensure a seamless and reliable webhook integration with Lahza.

### Supported events <a href="#supported-events" id="supported-events"></a>

{% tabs %}
{% tab title="First Tab" %}

{% endtab %}

{% tab title="Second Tab" %}

{% endtab %}
{% endtabs %}

### Types of events <a href="#types-of-events" id="types-of-events"></a>

| Event               | Description                                                                  |
| ------------------- | ---------------------------------------------------------------------------- |
| `charge.success`    | A successful charge was made                                                 |
| `refund.failed`     | Refund cannot be processed. Your account will be credited with refund amount |
| `refund.pending`    | Refund initiated, waiting for response from the processor.                   |
| `refund.processed`  | Refund has successfully been processed by the processor.                     |
| `refund.processing` | Refund has been received by the processor.                                   |

<br>

<br>

<br>


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://docs.lahza.io/payments/webhooks.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
