Skip to main content
All webhooks from Gnosis Ramp include a cryptographic signature. Always verify this signature before processing webhook events.

Headers

HeaderDescription
X-GnosisRamp-SignatureHMAC-SHA256 signature (hex-encoded)
X-GnosisRamp-TimestampISO 8601 timestamp used in signature
X-GnosisRamp-Event-TypeEvent type (e.g., INTENT_STATUS_CHANGED)
X-GnosisRamp-Client-IdYour project’s client ID

Signature Algorithm

The signature is computed as:
HMAC-SHA256(
  key = client_secret,
  message = timestamp + "." + request_body
)

Verification Steps

  1. Extract the timestamp and signature from headers
  2. Construct the signed payload: timestamp + "." + rawBody
  3. Compute HMAC-SHA256 using your client secret
  4. Compare signatures using timing-safe comparison
  5. Validate timestamp is within plus or minus 5 minutes

Node.js Example

const crypto = require('crypto');

function verifyWebhook(req, clientSecret) {
  const signature = req.headers['x-gnosisramp-signature'];
  const timestamp = req.headers['x-gnosisramp-timestamp'];
  const rawBody = req.rawBody; // Must preserve raw body

  // 1. Validate timestamp (within 5 minutes)
  const timestampDate = new Date(timestamp);
  const now = new Date();
  const diffMs = Math.abs(now - timestampDate);
  if (diffMs > 5 * 60 * 1000) {
    throw new Error('Timestamp too old');
  }

  // 2. Compute expected signature
  const signedPayload = `${timestamp}.${rawBody}`;
  const expectedSignature = crypto
    .createHmac('sha256', clientSecret)
    .update(signedPayload)
    .digest('hex');

  // 3. Compare using timing-safe comparison
  const signatureBuffer = Buffer.from(signature, 'hex');
  const expectedBuffer = Buffer.from(expectedSignature, 'hex');

  if (!crypto.timingSafeEqual(signatureBuffer, expectedBuffer)) {
    throw new Error('Invalid signature');
  }

  return true;
}

Express.js Middleware

const express = require('express');
const app = express();

// Preserve raw body for signature verification
app.use(express.json({
  verify: (req, res, buf) => {
    req.rawBody = buf.toString();
  }
}));

app.post('/webhooks/gnosis-ramp', (req, res) => {
  try {
    verifyWebhook(req, process.env.CLIENT_SECRET);

    // Process the event
    const { eventType, data } = req.body;
    console.log(`Received ${eventType}:`, data);

    res.status(200).send('OK');
  } catch (error) {
    console.error('Webhook verification failed:', error);
    res.status(401).send('Unauthorized');
  }
});

Python Example

import hmac
import hashlib
from datetime import datetime, timedelta, timezone

def verify_webhook(headers, body, client_secret):
    signature = headers.get('X-GnosisRamp-Signature')
    timestamp = headers.get('X-GnosisRamp-Timestamp')

    # 1. Validate timestamp (within 5 minutes)
    timestamp_dt = datetime.fromisoformat(timestamp.replace('Z', '+00:00'))
    now = datetime.now(timezone.utc)
    if abs((now - timestamp_dt).total_seconds()) > 300:
        raise ValueError('Timestamp too old')

    # 2. Compute expected signature
    signed_payload = f"{timestamp}.{body}"
    expected_signature = hmac.new(
        client_secret.encode(),
        signed_payload.encode(),
        hashlib.sha256
    ).hexdigest()

    # 3. Compare using timing-safe comparison
    if not hmac.compare_digest(signature, expected_signature):
        raise ValueError('Invalid signature')

    return True

Flask Example

from flask import Flask, request

app = Flask(__name__)

@app.route('/webhooks/gnosis-ramp', methods=['POST'])
def webhook():
    try:
        verify_webhook(
            request.headers,
            request.get_data(as_text=True),
            os.environ['CLIENT_SECRET']
        )

        event = request.json
        print(f"Received {event['eventType']}: {event['data']}")

        return 'OK', 200
    except ValueError as e:
        return str(e), 401

Common Issues

Raw Body Not Preserved

Many frameworks parse JSON before your handler runs. You must preserve the raw body string for signature verification. See the middleware examples above.

Timestamp Validation Failing

  • Ensure your server clock is synchronized (use NTP)
  • Allow for some clock skew (5 minutes is recommended)
  • Check that you’re parsing the ISO 8601 timestamp correctly

Next Steps

Event Reference

See all event types and payloads.

Webhooks Overview

Webhook setup and delivery details.