DEVELOPER GUIDE

Coming soon

Webhook Verification Guide

Verifying Webhook Authenticity

A JWT in the Authorization header of the request allows you to validate that the request originated from the pointservies platform. The JWT includes claims and a signature.

The JWT is an OpenIDConnect JWT that consists of a header, claim set, and signature. The webhook service encodes the JWT as a base64 string with period delimiters.

For example, the following authorization header includes an encoded JWT:

"Authorization" : "Bearer eyJraWQiOiIxMjM0NSIsInR5cCI6IkpXVCIsImFsZyI6IlJTMjU2In0.eyJhdWQiOiJ0ZXN0LXRlbmFudCIsInN1YiI6InhwcHN8MTIyMyIsImVtYWlsX3ZlcmlmaWVkIjp0cnVlLCJ1c2VyX2lkIjoiMTIyMyIsImF1dGhfdGltZSI6MTcwODcxNTYwMiwiaXNzIjoiaHR0cHM6XC9cL3NlY3VyZXRva2VuLmdvb2dsZS5jb21cL3Rlc3QtdGVuYW50IiwiZXhwIjoxNzA4NzE5LCJpYXQiOjE3MDg3MTUsImVtYWlsIjoidGVzdHVzZXJAdGVuYW50LmNvbSJ9.dGVzdA==" 

The header and claim set are JSON strings. Once decoded, they take the following form:

{
  "kid": "12345",
  "typ": "JWT",
  "alg": "RS256"
}

{
  "aud": "test-tenant",
  "sub": "xpps|1223",
  "email_verified": true,
  "user_id": "1223",
  "auth_time": 1708715602,
  "iss": "https://securetoken.google.com/test-tenant",
  "exp": 1708719,
  "iat": 1708715,
  "email": "testuser@tenant.com"
}

Example Code

Here’s a simple example that demonstrates how to verify and process a webhook request:

const express = require('express');
const jwt = require('express-jwt');
const jwksRsa = require('jwks-rsa');

const app = express();
const port = 3000;

// Trusted issuer and audience
const trustedIssuer = 'https://securetoken.google.com/test-tenant';
const audience = 'test-tenant';

// Middleware to validate the JWT Bearer token
const checkJwt = jwt({
  secret: jwksRsa.expressJwtSecret({
    cache: true, // Enable caching of JWKs
    rateLimit: true,
    jwksRequestsPerMinute: 5, // Prevent attackers from requesting JWKs too frequently
    jwksUri: `${trustedIssuer}/.well-known/jwks.json`
  }),
  audience: audience,
  issuer: trustedIssuer,
  algorithms: ['RS256']
});

// Placeholder for business logic processing
function processBusinessLogic(req, res) {
  // Implement your business logic here
  console.log("Processing business logic for user:", req.user.sub);
  res.send("Business logic processed successfully");
}

// Your API endpoint that requires JWT validation
app.post('/receive-webhook', checkJwt, (req, res) => {
  processBusinessLogic(req, res);
});

// Error handling for unauthorized access
app.use((err, req, res, next) => {
  if (err.name === 'UnauthorizedError') {
    res.status(403).send('Invalid token');
  }
});

app.listen(port, () => {
  console.log(`Server listening at http://localhost:${port}`);
});

When validating a JWT from an OpenID Connect (OIDC) compliant issuer, it’s efficient to cache the JWKS (JSON Web Key Set) and refresh it at a set interval, such as hourly, to reduce network overhead and improve performance.

Caching JWKS

To avoid fetching the JWKS from the issuer every time a token needs to be validated, implement caching with a timed expiration.

Validating the JWT

If the token is expired, the issuer is not in the trusted list or the audience is not valid for the trusted issuer, the express-jwt middleware will raise an exception that will be handled by the express error handler. Once this exception is raised, your endpoint should return a 403 status code without processing the body of the webhook.

Responding to Webhooks

  • Your endpoint should respond with a 200 OK status code after successfully processing the webhook.
  • If your endpoint is unable to authorize the webhook, respond with a 403 Forbidden status code.
  • If your endpoint is unable to handle the webhook, respond with an appropriate error code (e.g., 500 Internal Server Error).

Copyright © Pitchpoint Solutions. All rights reserved.