Verify webhook signature

There are two types of webhooks that you can receive from Transcend:

  1. Identity enrichment webhooks
  2. Server Webhook Integration

Each of these webhooks uses the same form of authentication.

Receive a webhook by creating a route that listens for new POST requests at your pre-registered URL (please refer to either of the above guides to learn about registering your webhook URL). The webhook will contain a JSON payload in the request body and a header, x-sombra-token, containing an asymmetrically signed token.

Endpoint reference: JWT public key

Webhooks will always be signed with an asymmetric digital signature algorithm. This is a JSON Web Token → that is signed with a private key. You will verify the token with a public key that can be on your Sombra gateway.

  • Choose a JWT library for your language. It must support ES384.
  • Read the HTTP request header x-sombra-token.
  • Read the public key from Sombra at the path /public-keys/sombra-general-signing-key. We recommend caching this value. Note this is an authenticated route. You can read about making a request to our API here.
  • Using the verify function with the algorithm ES384, verify x-sombra-token against the public key.
  • Verify that the scope field in the signed body of the JWT is coreIdentifier.
  • For an additional security layer against replay attacks, you may optionally ensure that the jti claim on the JWT has never been processed by your server before, which requires an external data store in most cases. See the RFC for an overview of this claim.
  • Signature verification is an important security feature that ensures webhooks are coming from Transcend. Always make sure the signature is valid before proceeding with any operation.

  • It's important that you only use ES384 as the signature verification algorithm. Allowing other algorithms can open security vulnerabilities.

  • You can either hard code or programmatically read the public key. Both the public key as well as the public key URL are available in the Admin Dashboard. We will notify you in advance of key rotations.

// Libraries
const got = require('got');
const jwt = require('jsonwebtoken');

// URL of your Sombra gateway
const SOMBRA_URL = '{{yourOrganizationSombraURL}}';

/**
 * Only required if you're using multi-tenant Sombra (i.e., not self-hosting Sombra)
 *
 * API key for Transcend API issued at https://app.transcend.io/infrastructure/api-keys
 * NOTE: this is a secret. Please handle with care
 */
const TRANSCEND_API_KEY = '~~SECRET-TRANSCEND-API-KEY-SECRET~~';

// Global to cache the webhook signing public key
let cachedPublicKey;

/**
 * Helper to verify incoming webhook requests
 *
 * @param {string} signedToken - the `x-sombra-token` header, which is a JSON
 * Web Token asymmetrically signed with ES384.
 *
 * @returns {Object} - the signed body
 */
module.exports = async function verifyAndExtractWebhook(signedToken) {
  // Get the public key and cache it for next time.
  if (!cachedPublicKey) {
    try {
      const response = await got.get(
        `${SOMBRA_URL}/public-keys/sombra-general-signing-key`,
        {
          // Only required if you're using multi-tenant Sombra (i.e., not self-hosting Sombra)
          headers: {
            authorization: `Bearer ${TRANSCEND_API_KEY}`,
          },
        }
      );
      cachedPublicKey = response.body;
    } catch (err) {
      console.error('Failed to get public key:', err);
    }
  }

  // Verify webhook signature with the public key (ensure Transcend sent the request)
  const signedBody = jwt.verify(signedToken, cachedPublicKey, {
    // important to specify the algorithm
    algorithms: ['ES384'],
  });

  if (signedBody.scope !== 'coreIdentifier') {
    throw Error('Found JWT with incorrect scope for webhook requests');
  }

  return signedBody;
};

We have working examples on GitHub.