Hypertune
  • Introduction
  • Getting Started
    • Set up Hypertune
    • Next.js (App Router) quickstart
    • Next.js (Pages Router) quickstart
    • React quickstart
    • Remix quickstart
    • Gatsby quickstart
    • Vue quickstart
    • Nuxt quickstart
    • Node.js quickstart
    • React Native quickstart
    • JavaScript quickstart
    • Python quickstart
    • Rust quickstart
    • Go quickstart
    • Web quickstart
    • GraphQL quickstart
  • Example apps
    • Next.js and Vercel example app
  • Concepts
    • Architecture
    • Project
    • Schema
    • Flag lifecycle
    • Logic
    • Variables
    • Splits
    • A/B tests
    • Staged rollouts
    • Multivariate tests
    • Machine learning loops
    • Events
    • Funnels
    • Hypertune Edge
    • Reduction
    • SDKs
    • GraphQL API
    • Git-style version control
    • App configuration
  • Use Cases
    • Feature flags and A/B testing
    • Landing page optimization
    • In-app content management
    • Pricing plan management
    • Permissions, rules and limits
    • Optimizing magic numbers
    • Backend configuration
    • Product analytics
  • Integrations
    • Vercel Edge Config integration
    • Google Analytics integration
    • Segment integration
    • Webhooks
      • Creating webhooks
      • Handling webhooks
  • SDK Reference
    • Installation
    • Type-safe client generation
    • Initialization
    • Build-time logic snapshot
    • Hard-coded fallbacks
    • Local-only, offline mode
    • Hydrate from your own server
    • Wait for server initialization
    • Provide targeting attributes
    • Local, synchronous evaluation
    • Remote logging
    • Getting flag updates
    • Serverless environments
    • Vercel Edge Config
    • Custom logging
    • Shutting down
Powered by GitBook
On this page
  • Request structure
  • Processing webhooks
  • JavaScript example
  1. Integrations
  2. Webhooks

Handling webhooks

This page explains how webhook events from Hypertune should be handled by your server.

Request structure

When an event occurs in your project, we'll notify all the subscribed active webhooks.

We do this by sending them each a POST request to their payload URLs. This request has a body that contains information about the event, for example:

{
  "id": "QAq7JB7nE5tY8mS99qsmM",
  "createdAt": "2023-04-17T12:02:26.095Z",
  "type": "NEW_COMMIT",
  "project": {
    "id": "2489",
    "name": "demo-webhook project"
  },
  "commit": {
    "id": "2792",
    "message": "v5"
  },
  "user": {
    "id": "2431",
    "displayName": "Miraan Tabrez",
    "email": "miraan@hypertune.com"
  }
}

Additionally, it will include the X-Hypertune-Signature header which will be the HMAC-SHA-256 digest in hexadecimal format of the raw body, using the webhook secret.

We will retry webhook sends with exponential backoff until a limit, so your implementation should be idempotent and handle missing hooks.

Processing webhooks

To process a request you should:

  1. Verify it is from Hypertune. To do this, validate that the X-Hypertune-Signature matches the definition above, using the secret you configured when setting up your webhook.

  2. Within 10 seconds, respond with a successful status code: one in the range 200-299 inclusive.

JavaScript example

import express from "express";
import { createHmac, timingSafeEqual } from "node:crypto";

// Do not hardcode in a production application
const WEBHOOK_SECRET = "1de87274cc2ddba774cbcec5a9ad8727";

const app = express();

// If you want to use body parser JSON, do something like this:
// https://github.com/stripe/stripe-node/issues/341#issuecomment-304733080
app.use(express.raw({ type: "*/*" }));

app.post("/", (req, res) => {
  const actualSignature = [req.headers["x-hypertune-signature"]].flat()[0];
  if (!actualSignature) {
    console.log("Rejected webhook request with missing signature");
    res.status(401).send("Missing X-Hypertune-Signature header");
    return;
  }

  const expectedSignature = createHmac("sha256", WEBHOOK_SECRET)
    .update(req.body.toString())
    .digest("hex");
  if (
    actualSignature.length !== expectedSignature.length ||
    !timingSafeEqual(
      Buffer.from(actualSignature),
      Buffer.from(expectedSignature)
    )
  ) {
    console.log("Rejected webhook request with bad signature");
    res.status(403).send("Bad signature");
    return;
  }

  console.log("Received valid webhook event");
  const data = JSON.parse(req.body.toString());
  // TODO: Do something interesting with the data here

  res.sendStatus(204);
});

const port = 8080;

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

Last updated 10 months ago