Verify Webhook Requests
How to verify BagelPay signature on webhook objects.
Last updated
How to verify BagelPay signature on webhook objects.
Last updated
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))
import express from 'express';
import crypto from 'crypto';
import ngrok from 'ngrok';
const app = express();
const WEBHOOK_SECRET = 'your_webhook_key';
// Middleware to parse raw body for signature verification
app.use('/api/webhooks', express.raw({ type: 'application/json' }));
function verifyWebhookSignature(signatureData: Buffer, signature: string, secret: string): boolean {
/**
* Verify webhook signature for security
*/
const expectedSignature = crypto
.createHmac('sha256', secret)
.update(signatureData)
.digest('hex');
return crypto.timingSafeEqual(
Buffer.from(expectedSignature, 'hex'),
Buffer.from(signature, 'hex')
);
}
app.post('/api/webhooks', async (req, res) => {
/**
* Handle BagelPay webhook notifications
*/
const payload = req.body;
const timestamp = req.headers['timestamp'] as string;
const signature = req.headers['bagelpay-signature'] as string;
// Combine payload and timestamp
const signatureData = Buffer.concat([
Buffer.from(timestamp, 'utf8'),
Buffer.from('.', 'utf8'),
payload
]);
if (!verifyWebhookSignature(signatureData, signature, WEBHOOK_SECRET)) {
return res.status(401).json({ error: 'Invalid signature' });
}
try {
const event = JSON.parse(payload.toString());
const eventType = event.event_type;
const data = event.object;
switch (eventType) {
case 'checkout.completed':
// Handle checkout completed events
console.log('Checkout completed:', event);
break;
case 'checkout.failed':
// Handle checkout failed events
console.log('Checkout failed:', event);
break;
case 'checkout.cancel':
// Handle checkout cancelled events
console.log('Checkout cancelled:', event);
break;
case 'subscription.trialing':
// Handle subscription trialing events
console.log('Subscription trialing:', event);
break;
case 'subscription.paid':
// Handle subscription paid events
console.log('Subscription paid:', event);
break;
case 'subscription.canceled':
// Handle subscription cancelled events
console.log('Subscription cancelled:', event);
break;
case 'refund.created':
// Handle refund created events
console.log('Refund created:', event);
break;
default:
console.log(`Unhandled event type: ${eventType}`);
}
return res.status(200).json({ message: 'Success' });
} catch (error) {
console.error('Webhook processing error:', error);
return res.status(500).json({ error: 'Processing failed' });
}
});
// Start server with ngrok tunnel
async function startServer() {
const listeningPort = 8000;
try {
// Set your ngrok auth token
await ngrok.authtoken('your_ngrok_key');
const publicUrl = await ngrok.connect({
addr: listeningPort,
proto: 'http',
subdomain: 'stunning-crane-direct' // Optional: use your reserved subdomain
});
console.log(`ngrok Public URL: ${publicUrl}`);
app.listen(listeningPort, '0.0.0.0', () => {
console.log(`Server running on port ${listeningPort}`);
});
} catch (error) {
console.error('Failed to start server:', error);
}
}
if (require.main === module) {
startServer();
}