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

# Webhooks

> Learn how to use webhooks to make your application react to events from Chargily Pay.

## What is a Webhook?

A webhook is an HTTP request, triggered by an event in a source system and sent to a destination system, with a payload of data (an event object).

## Why do we need Webhooks?

You'll need your website or application to promptly receive events from Chargily Pay as they occur, enabling the execution of corresponding actions.

For example, when a customer successfully pays for an order, your website or application should be informed to update the order status to "Paid."

To inform your website or application, Chargily Pay sends a webhook, which is a HTTPS POST request with a JSON payload.

## Example webhook payload

Here is an example of a webhook payload of a successfully paid checkout:

```json Event Payload Example theme={null}
{
  "id": "01hjjjzf7wbc454te45mwx35fe",
  "entity": "event",
  "livemode": "false",
  "type": "checkout.paid",
  "data": {
    "id": "01hjjj9aymmrwe664nbzrv84sg",
    "entity": "checkout",
    "fees": 1250,
    "amount": 50000,
    "locale": "ar",
    "status": "paid",
    "metadata": null,
    "created_at": 1703577693,
    "invoice_id": null,
    "updated_at": 1703578418,
    "customer_id": "01hjjjzf07chnbkcjax2vs58fv",
    "description": null,
    "failure_url": null,
    "success_url": "https://your-cool-website.com/payments/success",
    "payment_method": null,
    "payment_link_id": null,
    "pass_fees_to_customer": null,
    "chargily_pay_fees_allocation": "customer",
    "shipping_address": null,
    "collect_shipping_address": 1,
    "discount": null,
    "amount_without_discount": null,
    "url": "https://pay.chargily.dz/test/checkouts/01hjjj9aymmrwe664nbzrv84sg/pay"
  },
  "created_at": 1703578418,
  "updated_at": 1703578418
}
```

## The structure of a webhook's payload

**type**:
The `type` key in the payload indicates the type of event that occurred, `checkout.paid` in the previous example which means that the event was triggered by a successfully paid checkout.

**data**:
The `data` key in the payload contains the object related to the event, the object that triggered the event. In the previous example, the event object is a Checkout.

## Create your webhook endpoint

You need to set up an endpoint on your backend that accepts POST requests so that Chargily Pay can send you the webhooks.

### What should your endpoint do

<Steps>
  <Step title="Verifying the signature">
    For security reasons, every webhook request sent to your endpoint from
    Chargily Pay will have a header called `signature`, which is a **HMAC** of
    the payload signed with your API secret key. You need to verify it to make
    sure that the request came from us and that the payload hasn't been tampered
    with.
  </Step>

  <Step title="Identify the event">
    Each request's payload comes with a `type` key that indicates the type of
    event that occurred, you can use it to identify the event and take the
    appropriate action.
  </Step>

  <Step title="Handle the event">
    Once you've identified the event, you can handle it by executing the
    necessary actions.
  </Step>

  <Step title="Respond with a 200 response">
    After you've handled the event, you need to respond with a 200 response to
    let us know that you've received the webhook.
  </Step>
</Steps>

### Example endpoint

Here is an example of a webhook endpoint:

<CodeGroup>
  ```python Python theme={null}
  import hashlib
  import hmac
  import json
  from django.http import HttpResponse, JsonResponse
  from django.views.decorators.csrf import csrf_exempt
  from django.views.decorators.http import require_POST

  # Your Chargily Pay Secret key, will be used to calculate the Signature
  api_secret_key = 'test_sk_Fje5EhFwyGTGqk4M6et3Jxxxxxxxxxxxxxxxxxxxx'

  @csrf_exempt
  @require_POST
  def webhook(request):
      # Extracting the 'signature' header from the HTTP request
      signature = request.headers.get('signature')

      # Getting the raw payload from the request body
      payload = request.body.decode('utf-8')

      # If there is no signature, ignore the request
      if not signature:
          return HttpResponse(status=400)

      # Calculate the signature
      computed_signature = hmac.new(api_secret_key.encode('utf-8'), payload.encode('utf-8'), hashlib.sha256).hexdigest()

      # If the calculated signature doesn't match the received signature, ignore the request
      if not hmac.compare_digest(signature, computed_signature):
          return HttpResponse(status=403)

      # If the signatures match, proceed to decode the JSON payload
      event = json.loads(payload)

      # Switch based on the event type
      if event['type'] == 'checkout.paid':
          checkout = event['data']
          # Handle the successful payment.
      elif event['type'] == 'checkout.failed':
          checkout = event['data']
          # Handle the failed payment.

      # Respond with a 200 OK status code to let us know that you've received the webhook
      return JsonResponse({}, status=200)
  ```

  ```javascript javaScript theme={null}
  const express = require("express");
  const crypto = require("crypto");

  const app = express();
  const port = 3000;

  // Your Chargily Pay Secret key, will be used to calculate the Signature
  const apiSecretKey = "test_sk_Fje5EhFwyGTGqk4M6et3Jxxxxxxxxxxxxxxxxxxxx";

  app.use(express.json());

  app.post("/webhook", (req, res) => {
    // Extracting the 'signature' header from the HTTP request
    const signature = req.get("signature");

    // Getting the raw payload from the request body
    const payload = JSON.stringify(req.body);

    // If there is no signature, ignore the request
    if (!signature) {
      return res.sendStatus(400);
    }

    // Calculate the signature
    const computedSignature = crypto
      .createHmac("sha256", apiSecretKey)
      .update(payload)
      .digest("hex");

    // If the calculated signature doesn't match the received signature, ignore the request
    if (computedSignature !== signature) {
      return res.sendStatus(403);
    }

    // If the signatures match, proceed to decode the JSON payload
    const event = req.body;

    // Switch based on the event type
    switch (event.type) {
      case "checkout.paid":
        const checkout = event.data;
        // Handle the successful payment.
        break;
      case "checkout.failed":
        const failedCheckout = event.data;
        // Handle the failed payment.
        break;
    }

    // Respond with a 200 OK status code to let us know that you've received the webhook
    res.sendStatus(200);
  });

  app.listen(port, () => {
    console.log(`Server is running at http://localhost:${port}`);
  });
  ```

  ```php PHP theme={null}
  <?php

  // Your Chargily Pay Secret key, will be used to calculate the Signature
  $apiSecretKey = 'test_sk_Fje5EhFwyGTGqk4M6et3Jxxxxxxxxxxxxxxxxxxxx';

  // Extracting the 'signature' header from the HTTP request
  $signature = isset($_SERVER['HTTP_SIGNATURE']) ? $_SERVER['HTTP_SIGNATURE'] : null;

  // Getting the raw payload from the request body
  $payload = file_get_contents('php://input');

  // If there is no signature, exit the script (we will never send requests without a signature - a request without a signature is always a fake request so just ignore it)
  if (!$signature) {
      exit;
  }

  // Calculate the signature
  $computedSignature = hash_hmac('sha256', $payload, $apiSecretKey);

  // If the calculated signature doesn't match the received signature, exit the script (a request with a wrong signature means that the request has been tampered with so just ignore it)
  if (!hash_equals($signature, $computedSignature)) {
      exit();
  } else {
      // If the signatures match, proceed to decode the JSON payload
      $event = json_decode($payload);

      // Switch based on the event type
      switch ($event->type) {
          case 'checkout.paid':
              $checkout = $event->data;
              // Handle the successful payment.
              break;
          case 'checkout.canceled':
              $checkout = $event->data;
              // Handle the canceled payment.
              break;
          case 'checkout.failed':
              $checkout = $event->data;
              // Handle the failed payment.
              break;
      }
  }

  // Respond with a 200 OK status code to let us know that you've received the webhook
  http_response_code(200);
  ?>
  ```

  ```go Go theme={null}
  package main

  import (
  	"crypto/hmac"
  	"crypto/sha256"
  	"encoding/hex"
  	"encoding/json"
  	"io/ioutil"
  	"net/http"
  )

  // Your Chargily Pay Secret key, will be used to calculate the Signature
  const apiSecretKey = "test_sk_Fje5EhFwyGTGqk4M6et3Jxxxxxxxxxxxxxxxxxxxx"

  func main() {
  	http.HandleFunc("/webhook", webhookHandler)

  	port := 3000
  	serverAddress := ":3000"

  	http.ListenAndServe(serverAddress, nil)
  }

  func webhookHandler(w http.ResponseWriter, r *http.Request) {
  	// Extracting the 'signature' header from the HTTP request
  	signature := r.Header.Get("signature")

  	// Getting the raw payload from the request body
  	payload, err := ioutil.ReadAll(r.Body)
  	if err != nil {
  		http.Error(w, "Error reading request body", http.StatusInternalServerError)
  		return
  	}

  	// If there is no signature, ignore the request
  	if signature == "" {
  		http.Error(w, "No signature provided", http.StatusBadRequest)
  		return
  	}

  	// Calculate the signature
  	computedSignature := computeHMAC(payload, apiSecretKey)

  	// If the calculated signature doesn't match the received signature, ignore the request
  	if !hmac.Equal([]byte(computedSignature), []byte(signature)) {
  		http.Error(w, "Invalid signature", http.StatusForbidden)
  		return
  	}

  	// If the signatures match, proceed to decode the JSON payload
  	var event map[string]interface{}
  	if err := json.Unmarshal(payload, &event); err != nil {
  		http.Error(w, "Error decoding JSON payload", http.StatusInternalServerError)
  		return
  	}

  	// Switch based on the event type
  	switch event["type"] {
  	case "checkout.paid":
  		checkout := event["data"].(map[string]interface{})
  		// Handle the successful payment.
  	case "checkout.failed":
  		checkout := event["data"].(map[string]interface{})
  		// Handle the failed payment.
  	}

  	// Respond with a 200 OK status code to let us know that you've received the webhook
  	w.WriteHeader(http.StatusOK)
  }

  func computeHMAC(data []byte, key string) string {
  	h := hmac.New(sha256.New, []byte(key))
  	h.Write(data)
  	return hex.EncodeToString(h.Sum(nil))
  }
  ```

  ```java Java theme={null}
  import spark.Request;
  import spark.Response;
  import spark.Spark;

  import javax.crypto.Mac;
  import javax.crypto.spec.SecretKeySpec;
  import java.nio.charset.StandardCharsets;
  import java.security.InvalidKeyException;
  import java.security.NoSuchAlgorithmException;

  public class WebhookReceiver {

      // Your Chargily Pay Secret key, will be used to calculate the Signature
      private static final String apiSecretKey = "test_sk_Fje5EhFwyGTGqk4M6et3Jxxxxxxxxxxxxxxxxxxxx";

      public static void main(String[] args) {
          Spark.port(3000);

          Spark.post("/webhook", (req, res) -> handleWebhook(req, res));
      }

      private static Object handleWebhook(Request req, Response res) {
          // Extracting the 'signature' header from the HTTP request
          String signature = req.headers("signature");

          // Getting the raw payload from the request body
          String payload = req.body();

          // If there is no signature, ignore the request
          if (signature == null || signature.isEmpty()) {
              Spark.halt(400, "Bad Request");
              return null;
          }

          // Calculate the signature
          String computedSignature = computeHmacSha256(payload, apiSecretKey);

          // If the calculated signature doesn't match the received signature, ignore the request
          if (!computedSignature.equals(signature)) {
              Spark.halt(403, "Forbidden");
              return null;
          }

          // If the signatures match, proceed to decode the JSON payload
          // Note: You may want to handle JSON decoding based on your specific payload structure
          // For simplicity, we are not decoding JSON in this example
          System.out.println("Received payload: " + payload);

          // Respond with a 200 OK status code to let us know that you've received the webhook
          res.status(200);
          return "OK";
      }

      private static String computeHmacSha256(String data, String key) {
          try {
              Mac sha256Hmac = Mac.getInstance("HmacSHA256");
              SecretKeySpec secretKey = new SecretKeySpec(key.getBytes(StandardCharsets.UTF_8), "HmacSHA256");
              sha256Hmac.init(secretKey);
              byte[] hashBytes = sha256Hmac.doFinal(data.getBytes(StandardCharsets.UTF_8));
              return bytesToHexString(hashBytes);
          } catch (NoSuchAlgorithmException | InvalidKeyException e) {
              // Handle exceptions according to your application's requirements
              e.printStackTrace();
              return null;
          }
      }

      private static String bytesToHexString(byte[] bytes) {
          StringBuilder hexString = new StringBuilder();
          for (byte b : bytes) {
              String hex = Integer.toHexString(0xff & b);
              if (hex.length() == 1) {
                  hexString.append('0');
              }
              hexString.append(hex);
          }
          return hexString.toString();
      }
  }
  ```
</CodeGroup>

## Register your endpoint URL

To register your endpoint URL, go to [the Developers Corner page in the Chargily Pay Dashboard](http://pay.chargily.com/dashboard/developers-corner "the Developers Corner in the Chargily Pay Dashboard") and add the URL of your endpoint.

Or you can pass the URL of your endpoint in the `webhook_url` parameter when creating a checkout and we will use it as the endpoint URL for that checkout.

![Chargily Pay webhook endpoint setting](https://i.imgur.com/QFRmN4H.png "Chargily Pay webhook endpoint setting")

## Test your webhook endpoint locally

Chargily Pay can't send webhooks to your local server, so to test your webhook endpoint locally, you can use a tool like [ngrok](https://ngrok.com/ "ngrok") to make your local endpoint publicly accessible.
