Skip to main content

Signature Verification

Every webhook delivery includes a signature that you should verify to ensure the payload was sent by urelay and has not been tampered with.

How it works

When you create a webhook, urelay generates a unique signing secret. Each webhook delivery includes a signature computed from the request body using HMAC-SHA256 with your signing secret.

Verifying signatures

Node.js

const crypto = require('crypto');

function verifyWebhookSignature(body, signature, secret) {
const expected = crypto
.createHmac('sha256', secret)
.update(body, 'utf-8')
.digest('hex');
return crypto.timingSafeEqual(
Buffer.from(signature),
Buffer.from(expected)
);
}

// In your webhook handler:
app.post('/webhook', (req, res) => {
const signature = req.headers['x-urelay-signature'];
const isValid = verifyWebhookSignature(
JSON.stringify(req.body),
signature,
process.env.URELAY_WEBHOOK_SECRET
);

if (!isValid) {
return res.status(401).send('Invalid signature');
}

// Process the webhook...
res.status(200).send('OK');
});

Python

import hmac
import hashlib

def verify_webhook(body: bytes, signature: str, secret: str) -> bool:
expected = hmac.new(
secret.encode(),
body,
hashlib.sha256
).hexdigest()
return hmac.compare_digest(signature, expected)

# In your webhook handler:
@app.route('/webhook', methods=['POST'])
def webhook():
signature = request.headers.get('X-Urelay-Signature', '')
if not verify_webhook(request.data, signature, WEBHOOK_SECRET):
return 'Invalid signature', 401

payload = request.json
# Process the webhook...
return 'OK', 200

Security considerations

  • Always verify signatures in production
  • Use constant-time comparison (timingSafeEqual / compare_digest) to prevent timing attacks
  • If a signature is invalid, reject the request with a 401 status
  • Keep your signing secret confidential -- if compromised, rotate the webhook