Skip to main content

Webhook Security

Every webhook delivery from Orbit includes an HMAC-SHA256 signature in the X-Devotel-Signature header. Always verify this signature to ensure the request genuinely came from Orbit and hasn’t been tampered with.

How Signature Verification Works

  1. Orbit computes an HMAC-SHA256 hash of the raw request body using your webhook signing secret
  2. The hash is sent in the X-Devotel-Signature header as a hex string
  3. Your server recomputes the hash and compares it to the header value

Verification Examples

Node.js

import crypto from 'node:crypto';

function verifyWebhookSignature(payload, signature, secret) {
  const expected = crypto
    .createHmac('sha256', secret)
    .update(payload, 'utf-8')
    .digest('hex');

  return crypto.timingSafeEqual(
    Buffer.from(signature, 'hex'),
    Buffer.from(expected, 'hex')
  );
}

// In your webhook handler:
app.post('/webhooks/orbit', (req, res) => {
  const signature = req.headers['x-devotel-signature'];
  const isValid = verifyWebhookSignature(req.rawBody, signature, 'whsec_your_secret');

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

  const event = JSON.parse(req.rawBody);
  // Process the event...

  res.status(200).json({ received: true });
});

Python

import hmac
import hashlib

def verify_webhook_signature(payload: bytes, signature: str, secret: str) -> bool:
    expected = hmac.new(
        secret.encode('utf-8'),
        payload,
        hashlib.sha256
    ).hexdigest()

    return hmac.compare_digest(signature, expected)

# In your webhook handler (Flask example):
@app.route('/webhooks/orbit', methods=['POST'])
def handle_webhook():
    signature = request.headers.get('X-Devotel-Signature')
    is_valid = verify_webhook_signature(request.data, signature, 'whsec_your_secret')

    if not is_valid:
        return jsonify({'error': 'Invalid signature'}), 401

    event = request.get_json()
    # Process the event...

    return jsonify({'received': True}), 200

Go

package main

import (
	"crypto/hmac"
	"crypto/sha256"
	"encoding/hex"
)

func verifyWebhookSignature(payload []byte, signature, secret string) bool {
	mac := hmac.New(sha256.New, []byte(secret))
	mac.Write(payload)
	expected := hex.EncodeToString(mac.Sum(nil))

	return hmac.Equal([]byte(signature), []byte(expected))
}

Security Best Practices

  • Always verify signatures — never process webhooks without checking the signature
  • Use timing-safe comparison — prevents timing attacks (crypto.timingSafeEqual in Node.js, hmac.compare_digest in Python)
  • Use HTTPS — your webhook endpoint must use HTTPS to protect payloads in transit
  • Rotate secrets — rotate your webhook signing secret periodically in Settings > Webhooks
  • Idempotency — use the event id field to deduplicate, since Orbit guarantees at-least-once delivery
  • Respond quickly — return a 2xx within 30 seconds; process heavy work asynchronously
  • IP allowlisting — optionally restrict incoming webhooks to Orbit’s IP ranges (available in the dashboard under Settings > Security)

Signing Secret

Your webhook signing secret is generated when you create a webhook. Retrieve it from the dashboard under Webhooks > [Your Webhook] > Signing Secret, or via the API:
curl https://orbit-api.devotel.io/api/v1/webhooks/wh_abc123 \
  -H "X-API-Key: dv_live_sk_..."
The secret is prefixed with whsec_ and should be stored securely — never expose it in client-side code or logs.