Documentation

Webhook Signatures

Learn how to verify webhook authenticity and protect against spoofing attacks

Overview

Every webhook request includes a signature in the header that you can verify to ensure the request genuinely came from Yolfi and wasn't tampered with during transit.

Always verify webhook signatures before processing any webhook event. Failing to do so could allow attackers to send fake payment notifications to your system.


Why Verify Signatures?

Signature verification provides three key security guarantees:

  1. Authenticity - Confirms the webhook was sent by Yolfi, not an imposter
  2. Integrity - Ensures the payload wasn't modified in transit
  3. Replay Protection - Prevents attackers from reusing old valid webhooks

How It Works

Signature Generation

When we send a webhook, we:

  1. Create a JSON payload with the event data
  2. Generate an HMAC-SHA256 signature using your API key as the secret
  3. Encode the signature in Base64
  4. Send it in the request headers

Headers

Each webhook request includes these headers:

HeaderDescription
Content-TypeAlways application/json
X-Yolfi-SignatureBase64-encoded HMAC-SHA256 signature
X-Yolfi-Event-IDUnique event identifier

Verification Process

To verify a webhook signature:

Extract the X-Yolfi-Signature header from the request.

Retrieve your API key from your organization settings.

Calculate HMAC-SHA256 of the raw request body using your API key.

Use constant-time comparison to compare your computed signature with the one in the header.


Code Examples

const crypto = require('crypto');

function verifyWebhookSignature(payload, signature, apiKey) {
  if (!signature || !apiKey) {
    return false;
  }

  const expected = crypto
    .createHmac('sha256', apiKey)
    .update(payload, 'utf8')
    .digest('base64');

  try {
    return crypto.timingSafeEqual(
      Buffer.from(signature, 'base64'),
      Buffer.from(expected, 'base64')
    );
  } catch (e) {
    return false;
  }
}

// Express.js example
app.post('/webhook', express.raw({ type: 'application/json' }), (req, res) => {
  const signature = req.headers['x-yolfi-signature'];
  const payload = req.body.toString(); // Keep raw body as string
  const apiKey = process.env.YOLFI_API_KEY;

  const isValid = verifyWebhookSignature(payload, signature, apiKey);

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

  const event = JSON.parse(payload);
  console.log('Received webhook:', event.type);

  // Process the event
  res.status(200).send('OK');
});
import hmac
import hashlib
import base64
import os

def verify_signature(payload: str, signature: str, api_key: str) -> bool:
    if not signature or not api_key:
        return False

    expected = hmac.new(
        api_key.encode('utf-8'),
        payload.encode('utf-8'),
        hashlib.sha256
    ).digest()

    return hmac.compare_digest(
        base64.b64decode(signature),
        expected
    )

# Flask example
@app.route('/webhook', methods=['POST'])
def webhook():
    signature = request.headers.get('X-Yolfi-Signature')
    payload = request.get_data(as_text=True)
    api_key = os.environ.get('YOLFI_API_KEY')

    if not verify_signature(payload, signature, api_key):
        return 'Invalid signature', 400

    event = request.get_json()
    print(f"Received webhook: {event['type']}")
    return 'OK', 200
import (
    "crypto/hmac"
    "crypto/sha256"
    "crypto/subtle"
    "encoding/base64"
    "os"
)

func verifySignature(payload string, signature string, apiKey string) bool {
    if signature == "" || apiKey == "" {
        return false
    }

    expected := hmac.New(sha256.New, []byte(apiKey))
    expected.Write([]byte(payload))

    expectedSig, _ := base64.StdEncoding.DecodeString(expected.Sum(nil))
    sig, _ := base64.StdEncoding.DecodeString(signature)

    return subtle.ConstantTimeCompare(expectedSig, sig) == 1
}

// Echo framework example
e.POST("/webhook", func(c echo.Context) error {
    signature := c.Request().Header.Get("X-Yolfi-Signature")
    payload, _ := io.ReadAll(c.Request().Body)
    apiKey := os.Getenv("YOLFI_API_KEY")

    if !verifySignature(string(payload), signature, apiKey) {
        return c.String(400, "Invalid signature")
    }

    var event map[string]interface{}
    json.Unmarshal(payload, &event)
    fmt.Printf("Received webhook: %v\n", event["type"])

    return c.String(200, "OK")
})

Security Best Practices

Always use constant-time comparison functions (crypto.timingSafeEqual in Node.js, hmac.compare_digest in Python) to prevent timing attacks.

Keep your API keys in environment variables or a secure secrets manager. Never commit them to version control.

Compute the signature over the raw request body, not a parsed/re-serialized version.

Verify the signature and return 200 OK immediately. Process the event asynchronously afterward.

Webhooks may be retried on failure. Design your handler to be idempotent - the same event should not cause duplicate actions.

On this page