# Verify Webhook Signature (/docs/dsr-automation/api-integration/receiving-a-webhook-from-transcend)

## Types of Webhooks [#types-of-webhooks]

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

1. [Identity Enrichment Webhooks](../preflight-and-enrichment/preflight-checks/preflight-check-post-to-a-server-webhook.mdx)
2. [Server Webhook Integration](../../integrations/custom/server-webhook-integration.mdx)

Each of these webhooks uses the same form of authentication.

## Set up a route on your server [#set-up-a-route-on-your-server]

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.

## Verify the webhook signature [#verify-the-webhook-signature]

[Endpoint reference: JWT public key](/docs/api-reference/public-keys/get-public-keys-sombra-general-signing-key)

Webhooks will always be signed with an asymmetric digital signature algorithm. This is a [JSON Web Token →](https://jwt.io) that is signed with a private key. You will verify the token with a public key that can be on your <GlossaryTerm id="sombra">Sombra</GlossaryTerm> gateway.

- [Choose a JWT library for your language](https://jwt.io/#libraries-io). 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](../../overview/developer-tools/authentication.mdx).
- 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](https://datatracker.ietf.org/doc/html/rfc7519#section-4.1.7) for an overview of this claim.

<Callout type="warn">
  ### Things to note [#things-to-note]

  - 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](https://auth0.com/blog/critical-vulnerabilities-in-json-web-token-libraries/).
  - 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.
</Callout>

## Sample code [#sample-code]

```javascript
// 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;
};
```

<Callout type="success">
  ### Check out our code examples [#check-out-our-code-examples]

  We have working examples on [GitHub](https://github.com/transcend-io/examples).
</Callout>
