Skip to main content

Validating Webhooks

BlockWyre signs all outbound webhooks using RS512 (RSA-SHA512) detached JWS signatures. This allows you to verify that webhooks are authentic and haven't been tampered with, without needing a shared secret.

How It Works​

  1. BlockWyre signs each webhook payload with an RSA private key
  2. The signature is sent as a detached JWS in the X-BlockWyre-Signature header
  3. You fetch BlockWyre's public key from the JWKS endpoint to verify the signature

JWKS Endpoint​

GET /.well-known/jwks.json

This endpoint is public (no authentication required) and returns BlockWyre's current public keys in JWK Set format.

Response:

{
"keys": [
{
"kty": "RSA",
"kid": "blockwyre-webhook-key-1",
"alg": "RS512",
"use": "sig",
"n": "...",
"e": "AQAB"
}
]
}

Caching: The endpoint returns Cache-Control: public, max-age=3600. Cache the JWKS for up to 1 hour and refresh on a kid miss (indicating key rotation).

Signature Format​

The signature is a detached JWS in compact serialization:

<base64url-header>..<base64url-signature>

Note the empty payload segment (two consecutive dots). The actual payload is the raw HTTP request body.

Header (decoded):

{
"alg": "RS512",
"kid": "blockwyre-webhook-key-1"
}

Signature Version​

VersionFormatAlgorithm
20260201 (current)Detached JWSRS512

Verification Steps​

  1. Fetch the JWKS from GET /.well-known/jwks.json (cache for 1 hour, refresh on kid miss)
  2. Extract the kid from the JWS header (base64url-decode the part before ..)
  3. Find the matching key in the JWKS by kid
  4. Reconstruct the full JWS by inserting the raw request body (base64url-encoded) between the two dots
  5. Verify the RS512 signature using the public key from JWKS
  6. Validate the timestamp from X-BlockWyre-Timestamp — reject if older than 5 minutes

Code Examples​

Node.js​

const jose = require('jose');

// Cache the JWKS - refresh every hour or on kid miss
let jwksCache = null;
let jwksCacheTime = 0;

async function getJWKS(baseUrl) {
const now = Date.now();
if (jwksCache && now - jwksCacheTime < 3600000) {
return jwksCache;
}
const response = await fetch(`${baseUrl}/.well-known/jwks.json`);
jwksCache = await response.json();
jwksCacheTime = now;
return jwksCache;
}

async function verifyWebhook(req, baseUrl) {
const signature = req.headers['x-blockwyre-signature'];
const signatureVersion = req.headers['x-blockwyre-signature-version'];
const timestamp = req.headers['x-blockwyre-timestamp'];

// Check signature version
if (signatureVersion !== '20260209') {
throw new Error(`Unsupported signature version: ${signatureVersion}`);
}

// Reject stale webhooks (> 5 minutes)
const age = Math.abs(Date.now() / 1000 - parseInt(timestamp));
if (age > 300) {
throw new Error('Webhook timestamp too old');
}

// Validate detached JWS format
const parts = signature.split('..');
if (parts.length !== 2) {
throw new Error('Invalid signature format');
}

// Reconstruct full JWS with payload
const rawBody = typeof req.body === 'string'
? req.body
: JSON.stringify(req.body);
const encodedPayload = Buffer.from(rawBody).toString('base64url');
const fullJWS = `${parts[0]}.${encodedPayload}.${parts[1]}`;

// Fetch JWKS and verify
const jwks = await getJWKS(baseUrl);
const { payload } = await jose.compactVerify(
fullJWS,
jose.createLocalJWKSet(jwks)
);

return JSON.parse(new TextDecoder().decode(payload));
}

Replay Attack Prevention​

tip

Always validate the X-BlockWyre-Timestamp header to prevent replay attacks. Reject any webhook with a timestamp older than 5 minutes from your server's current time.

Additionally, you can track the X-BlockWyre-Webhook-ID header to detect and reject duplicate deliveries.

Key Rotation​

BlockWyre may rotate signing keys periodically. When this happens:

  1. A new key pair is generated with a new kid
  2. The JWKS endpoint is updated to include the new public key
  3. New webhooks are signed with the new key
  4. The old public key remains in the JWKS for a transition period

Your implementation should handle this automatically if you:

  • Look up keys by kid (not by index)
  • Refresh the JWKS cache when you encounter an unknown kid

Support and Resources​

If you need assistance or have any questions, our support team is here to help. You can contact our support team at support@blockwyre.com.

Stay Updated​

Stay up-to-date with the latest news, updates, and features from BlockWyre by following us on social media:

We are excited to have you on board and look forward to seeing how you leverage BlockWyre's powerful tools to enhance your financial operations. Happy integrating!