Skip to content

Webhook Security

All webhook deliveries are signed using HMAC-SHA256 so you can verify that requests are genuinely from Replicer.

Signing mechanism

Each webhook endpoint has a unique signing secret (shown once at creation). Replicer signs every payload with this secret and includes the signature in the X-Replicer-Signature header.

The signature is computed as:

HMAC-SHA256(signing_secret, request_body)

Verifying signatures

Node.js

javascript
import crypto from 'crypto'

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

// Express middleware
app.post('/webhook', express.raw({ type: 'application/json' }), (req, res) => {
  const signature = req.headers['x-replicer-signature']
  const secret = process.env.WEBHOOK_SECRET

  if (!verifyWebhookSignature(req.body, signature, secret)) {
    return res.status(401).json({ error: 'Invalid signature' })
  }

  const event = JSON.parse(req.body)
  // Process the event...
  
  res.status(200).json({ received: true })
})

Python

python
import hmac
import hashlib

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

# Flask example
@app.route('/webhook', methods=['POST'])
def webhook():
    signature = request.headers.get('X-Replicer-Signature')
    secret = os.environ['WEBHOOK_SECRET']
    
    if not verify_webhook(request.data, signature, secret):
        return jsonify({'error': 'Invalid signature'}), 401
    
    event = request.get_json()
    # Process the event...
    
    return jsonify({'received': True}), 200

Go

go
func verifySignature(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))
}

Additional headers

Use these headers for extra verification:

HeaderDescription
X-Replicer-TimestampUnix timestamp of when the event was sent
X-Replicer-Delivery-IdUnique ID for this delivery attempt
X-Replicer-EventEvent type (e.g., call.ended)

Best practices

Security recommendations

  • Always verify signatures before processing webhook events
  • Use timingSafeEqual (or equivalent) to prevent timing attacks
  • Store secrets securely in environment variables or a secret manager
  • Respond quickly — process asynchronously and return 200 immediately
  • Be idempotent — you may receive the same event multiple times due to retries
  • Check the timestamp — reject events older than 5 minutes to prevent replay attacks

Timestamp verification

To prevent replay attacks, verify the X-Replicer-Timestamp header:

javascript
const timestamp = parseInt(req.headers['x-replicer-timestamp'])
const now = Math.floor(Date.now() / 1000)
const fiveMinutes = 5 * 60

if (Math.abs(now - timestamp) > fiveMinutes) {
  return res.status(401).json({ error: 'Timestamp too old' })
}

Replicer API Documentation