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

# Working with Webhooks

> Step-by-step guide to set up and implement webhooks in your application.

## Guidelines

To implement webhooks in your application, follow these essential steps:

<Steps titleSize="h3">
  <Step title="Configure Your Endpoint">
    Create a publicly accessible HTTP endpoint in your application that can receive POST requests. This endpoint must be available over HTTPS and return a 2xx status code to acknowledge receipt of webhook events.

    <Warning>
      Your webhook endpoint must be publicly accessible. For local development, use tools like [ngrok](https://ngrok.com/) to expose your local server.
    </Warning>
  </Step>

  <Step title="Configure Webhooks">
    Configure your webhook endpoint URL directly through the [Partners Dashboard](https://partners.gnosispay.com/) or contact your Gnosis Pay partner manager for assistance. Provide the complete HTTPS URL where you want to receive webhook notifications.

    <Info>
      **Partners Dashboard**: For partners registered through the dashboard, webhook configuration is available instantly in your dashboard settings.
      **Manual Setup**: If you need technical support assistance, webhook configuration typically takes 1-2 business days.
    </Info>
  </Step>

  <Step title="Receive and Verify Events">
    When events happen in the Gnosis Pay system, we'll send HTTP POST requests to your webhook endpoint with event data and cryptographic signatures.

    All webhooks include cryptographic signatures using **Ed25519 asymmetric cryptography**:

    * **`X-Webhook-Timestamp`**: Unix timestamp when the webhook was sent
    * **`X-Webhook-Signature`**: Base64-encoded Ed25519 signature

    <Warning>
      **Always verify webhook signatures** before processing events. This ensures
      the webhook originated from Gnosis Pay and hasn't been tampered with.
    </Warning>

    Retrieve the public key for signature verification from our API:

    ```bash cURL theme={null}
    curl -X GET https://webhooks.gnosispay.com/api/v1/public-key
    ```
  </Step>

  <Step title="Parse and Process Event Data">
    Extract the `eventType` and `data` fields from the webhook payload. The `eventType` identifies what happened (e.g., `user.created`, `kyc.status.changed`), while `data` contains the complete entity information.

    ```json theme={null}
    {
      "eventType": "user.created",
      "data": {
        "id": "user_123",
        "email": "user@example.com",
        // ... complete user entity
      }
    }
    ```

    Handle each event type appropriately in your application. Since we send complete entity data, you typically won't need additional API calls to get the full context.

    <Info>
      Process events idempotently to handle potential duplicates, and implement proper error handling and logging for monitoring.
    </Info>

    <Info>
      **Retry Policy**: If your webhook endpoint returns a non-2xx status code, we'll retry delivery up to 3 times with exponential backoff (1 minute, 5 minutes, 15 minutes).
    </Info>

    <Warning>
      **Timeout**: Your webhook endpoint must respond within 30 seconds. Requests that exceed this timeout are considered failed and will trigger our retry mechanism.
    </Warning>
  </Step>
</Steps>

## Complete Example

<CodeGroup>
  ```typescript Node.js theme={null}
  import express from "express";
  import crypto from "crypto";

  const app = express();

  // Use raw body parsing for webhook signature verification
  app.use("/webhook", express.raw({ type: "application/json" }));
  app.use(express.json());

  app.post("/webhook", async (req: express.Request, res: express.Response) => {
    try {
      // Verify the webhook signature
      const isValid = await verifyWebhookSignature(req);
      if (!isValid) {
        return res.status(401).send("Invalid signature");
      }

      // Parse the body after verification
      const payload = JSON.parse(req.body.toString());
      const { eventType, data } = payload;
      await processWebhookEvent(eventType, data);

      // Acknowledge receipt
      res.status(200).send("OK");
    } catch (error) {
      console.error("Webhook processing error:", error);
      res.status(500).send("Internal server error");
    }
  });

  async function verifyWebhookSignature(req: express.Request): Promise<boolean> {
    try {
      const signature = req.headers["x-webhook-signature"] as string;
      const timestamp = req.headers["x-webhook-timestamp"] as string;

      // Check if required headers are present
      if (!timestamp || !signature) {
        console.error(
          "Missing required headers: x-webhook-timestamp or x-webhook-signature"
        );
        return false;
      }

      // Use the raw body for signature verification
      const body = req.body.toString();

      // Retrieve the public key from Gnosis Pay
      const publicKeyResponse = await fetch(
        "https://webhooks.gnosispay.com/api/v1/public-key"
      );
      const keyData = await publicKeyResponse.json();

      if (!keyData.success || !keyData.publicKey) {
        throw new Error("Failed to fetch public key");
      }

      const { publicKey } = keyData;

      // Create the payload for verification (same as Bun implementation)
      const signingPayload = `${timestamp}.${body}`;

      // Verify the signature using the same method as Bun
      return crypto.verify(
        null,
        Buffer.from(signingPayload, "utf8"),
        publicKey,
        Buffer.from(signature, "base64")
      );
    } catch (error) {
      console.error("Signature verification failed:", error);
      return false;
    }
  }

  async function processWebhookEvent(eventType: string, data: any) {
    switch (eventType) {
      case "user.created":
        console.log("user.created", data);
        break;
      case "kyc.status.changed":
        console.log("kyc.status.changed", data);
        break;
      case "card.transaction.created":
        console.log("card.transaction.created", data);
        break;
      // Handle other event types
      default:
        console.log(`Unhandled event type: ${eventType}`);
    }
  }
  ```
</CodeGroup>
