Verify Signatures

Verify webhook authenticity and protect your endpoint.

❗️

Critical: Never process webhook payloads without verifying their authenticity when webhook signature verification is enabled.

Best practices

PracticeDetail
HTTPS onlyNever accept webhooks over plain HTTP
Verify signaturesValidate the X-DigetPay-Signature HMAC signature before processing the payload
Allowlist IPsRestrict inbound webhook traffic to DigetPay outbound IP ranges when available
Idempotent handlersDeduplicate events using transactionId + type
Fast 200 responseAcknowledge valid events within seconds and process longer business logic asynchronously
🚧

Important: Reject webhooks with invalid signatures using 401 Unauthorized — do not process the payload.

Signature verification flow

sequenceDiagram
    autonumber
    participant DigetPay as DigetPay
    participant Merchant as Your Server

    DigetPay->>Merchant: POST webhook + X-DigetPay-Signature
    Merchant->>Merchant: Verify webhook signature
    alt Signature valid
        Merchant->>Merchant: Accept and process event
        Merchant-->>DigetPay: 200 OK
    else Signature invalid
        Merchant-->>DigetPay: 401 Unauthorized
    end

Incoming request format

{
  "headers": {
    "Content-Type": "application/json",
    "X-DigetPay-Signature": "signature-value"
  },
  "body": {
    "transactionId": "2232e99b-0257-47d5-bbfd-022c8951767f",
    "orderId": "PAY-1781872369616",
    "status": "Approved",
    "type": "Sale"
  }
}
📘

The webhook signature must be verified using the exact signing mechanism provided by DigetPay. Do not modify, parse, or re-serialize the request body before verification if the signing mechanism requires the raw request body.

Signature verification

DigetPay webhook requests include the X-DigetPay-Signature header.

Merchants should verify this signature before processing any webhook event.

The exact signing algorithm, signed payload format, and secret-management flow must match the DigetPay webhook implementation.

Security note: Do not assume an algorithm, canonical payload, timestamp format, or secret format unless it has been confirmed by the DigetPay technical team.

Code examples

The following examples are illustrative only. They demonstrate the general structure of webhook signature verification and must be updated to match the confirmed DigetPay signing specification before being used in production.

<?php

$rawBody = file_get_contents('php://input');
$signature = $_SERVER['HTTP_X_DIGETPAY_SIGNATURE'] ?? '';
$webhookSecret = getenv('DIGETPAY_WEBHOOK_SECRET');

if (!$webhookSecret || !$signature) {
    http_response_code(401);
    exit('Unauthorized');
}

/*
 * Replace this verification logic with the exact
 * DigetPay signing specification once confirmed.
 */
$expectedSignature = hash_hmac(
    'sha256',
    $rawBody,
    $webhookSecret
);

if (!hash_equals($expectedSignature, $signature)) {
    http_response_code(401);
    exit('Unauthorized');
}

// Signature is valid.
// Process the webhook event.

http_response_code(200);
echo 'OK';
const crypto = require("crypto");

function verifySignature(rawBody, signature, webhookSecret) {
  if (!signature || !webhookSecret) {
    return false;
  }

  /*
   * Replace this verification logic with the exact
   * DigetPay signing specification once confirmed.
   */
  const expectedSignature = crypto
    .createHmac("sha256", webhookSecret)
    .update(rawBody, "utf8")
    .digest("hex");

  return crypto.timingSafeEqual(
    Buffer.from(expectedSignature),
    Buffer.from(signature)
  );
}

// The webhook route must have access to the raw request body.
app.post(
  "/webhooks/digetpay",
  express.raw({ type: "application/json" }),
  (req, res) => {
    const rawBody = req.body.toString("utf8");
    const signature = req.header("X-DigetPay-Signature");
    const webhookSecret = process.env.DIGETPAY_WEBHOOK_SECRET;

    if (!verifySignature(rawBody, signature, webhookSecret)) {
      return res.status(401).send("Unauthorized");
    }

    const payload = JSON.parse(rawBody);

    // Process the verified event asynchronously.

    return res.status(200).send("OK");
  }
);
import os
import hmac
import hashlib
from flask import Flask, request

app = Flask(__name__)

@app.post("/webhooks/digetpay")
def digetpay_webhook():
    raw_body = request.get_data()
    signature = request.headers.get("X-DigetPay-Signature", "")
    webhook_secret = os.environ.get("DIGETPAY_WEBHOOK_SECRET")

    if not webhook_secret or not signature:
        return "Unauthorized", 401

    /*
     * Replace this verification logic with the exact
     * DigetPay signing specification once confirmed.
     */
    expected_signature = hmac.new(
        webhook_secret.encode("utf-8"),
        raw_body,
        hashlib.sha256
    ).hexdigest()

    if not hmac.compare_digest(expected_signature, signature):
        return "Unauthorized", 401

    payload = request.get_json()

    # Process the verified event asynchronously.

    return "OK", 200
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;

public class DigetPayWebhookVerifier {

    public static boolean verifySignature(
            byte[] rawBody,
            String receivedSignature,
            String webhookSecret) throws Exception {

        if (receivedSignature == null || webhookSecret == null) {
            return false;
        }

        /*
         * Replace this verification logic with the exact
         * DigetPay signing specification once confirmed.
         */
        Mac mac = Mac.getInstance("HmacSHA256");

        SecretKeySpec secretKey = new SecretKeySpec(
                webhookSecret.getBytes(StandardCharsets.UTF_8),
                "HmacSHA256"
        );

        mac.init(secretKey);

        byte[] digest = mac.doFinal(rawBody);

        StringBuilder hex = new StringBuilder();

        for (byte b : digest) {
            hex.append(String.format("%02x", b));
        }

        return MessageDigest.isEqual(
                hex.toString().getBytes(StandardCharsets.UTF_8),
                receivedSignature.getBytes(StandardCharsets.UTF_8)
        );
    }
}
⚠️

The code examples above are reference implementations only. Confirm the exact DigetPay webhook signing contract with the DigetPay technical team before using them in production.

Replay protection

A secure webhook implementation should also consider replay attacks, where a previously valid webhook request is submitted again.

The current documentation does not confirm whether DigetPay includes a timestamp, nonce, or other replay-protection value in the webhook signature.

Therefore, do not implement timestamp validation or claim timestamp-based replay protection unless the DigetPay signing specification explicitly provides it.

Regardless of the signature mechanism, webhook handlers should be idempotent.

Use an appropriate event identifier, such as:

transactionId + type

to prevent the same webhook event from triggering duplicate business actions.

📘

If DigetPay supports timestamp-based signing, this section should be updated with the exact timestamp header, signing format, and allowed clock-skew window.

Webhook secret

The webhook signing secret must be stored securely and must never be exposed in frontend code, source control, or production logs.

Use a secrets manager or equivalent secure configuration mechanism.

{
  "webhookSecret": "store-in-secrets-manager",
  "storage": "Never commit to Git or log in production"
}
🚧

Do not use the examples above to assume a specific webhook-secret generation or rotation process until the DigetPay implementation is confirmed.

IP allowlisting

IP allowlisting can be used as an additional network-level security control.

Merchant infrastructure can restrict inbound webhook traffic to DigetPay's published outbound webhook IP ranges.

⚠️

IP allowlisting does not replace signature verification. When signature verification is enabled, always validate the webhook signature before processing the event.

Production documentation: DigetPay's authoritative outbound webhook IP ranges should be published here once confirmed by the DigetPay infrastructure team.

Response handling

For a valid webhook that has passed authentication and has been accepted for processing, return:

HTTP/1.1 200 OK

For a missing or invalid signature, return:

HTTP/1.1 401 Unauthorized

Do not process an unauthenticated webhook payload.

Keep webhook endpoints fast and move longer business operations to asynchronous processing where possible.

Security checklist

Before going live, confirm that:

  • The webhook endpoint uses HTTPS.
  • The X-DigetPay-Signature header is validated.
  • The exact DigetPay signing algorithm and payload format are implemented.
  • Signature comparison uses a timing-safe comparison method where supported.
  • The webhook secret is stored securely.
  • The webhook secret is never logged or committed to source control.
  • Duplicate events are handled idempotently.
  • Invalid signatures return 401 Unauthorized.
  • Valid events return 200 OK.
  • DigetPay outbound webhook IP ranges are allowlisted where required.
  • IP allowlisting is treated as an additional security control, not a replacement for signature verification.
  • Replay-protection requirements have been confirmed with the DigetPay technical team.

Related guides


Did this page help you?