Verify Webhook Requests

How to verify BagelPay signature on webhook objects.

How to verify BagelPay signature?

BagelPay signature is sent in the Bagelpay-Signature header of the webhook request. The signature is generated using the HMAC-SHA256 algorithm with the Signing Secret as the key, and the Request Body and Timestamp as the message.

Sample Webhook Header
{
	"Bagelpay-Signature": "dd7bdd2cf1f6bac6e171c6c508c157b7cd3cc1fd196394277fb59ba0bdd9b87b",
	"timestamp": "1756301826"
}

To verify the signature, you need to generate the signature using the same algorithm and compare it with the signature sent in the header.

If the two signatures match, the request is authentic.

You can find your webhook secret on the Developer>Webhook page.

To generate the Bagelpay-Signature, you can use the following code example:

import hmac
import hashlib

from fastapi import FastAPI, Request
from fastapi.responses import JSONResponse
import uvicorn
from pyngrok import ngrok
ngrok.set_auth_token("your_ngrok_key")
WEBHOOK_SECRET = "your_webhook_key"

app = FastAPI()


def verify_webhook_signature(signature_data: bytes, signature: str, secret: str) -> bool:
    """Verify webhook signature for security"""
    expected_signature = hmac.new(
        secret.encode('utf-8'),
        signature_data,
        hashlib.sha256
    ).hexdigest()

    print("expected_signature: ", expected_signature)
    print("signature: ", signature)

    return hmac.compare_digest(expected_signature, signature)


@app.post("/api/webhooks")
async def handle_post(request: Request):
    """Handle BagelPay webhook notifications"""
    payload = await request.body()
    timestamp = request.headers.get('timestamp').encode()
    signature = request.headers.get('Bagelpay-Signature')
    # Combine payload and timestamp
    signature_data = timestamp + ".".encode() + payload
    print("payload: ", payload)
    print("timestamp: ", timestamp)
    print("signature: ", signature)
    print("signature_data: ", signature_data)

    if not verify_webhook_signature(signature_data, signature, WEBHOOK_SECRET):
        return JSONResponse(status_code=401, content={"error": "Invalid signature"})

    print(payload)
    return JSONResponse(status_code=200, content={"message": "Success"})


if __name__ == "__main__":
    listening_port = "8000"
    public_url = ngrok.connect(
        addr=listening_port,
        proto="http",
        hostname="stunning-crane-direct.ngrok-free.app"
    )
    print(f"ngrok Public URL: {public_url}")
    uvicorn.run(app, host="0.0.0.0", port=int(listening_port))

Simply compare the generated Signature with the one received on the header to complete the verification process.

Last updated