Quickstart

Create a Happa plugin draft, verify signed requests, and receive your first test.ping in under 10 minutes.

Step 1 - Create a plugin draft

Open the Developer Portal and create a new plugin. For your first draft, use:

Save the draft and copy the generated webhookSecret. You will use it locally in the next step.

Step 2 - Install dependencies

mkdir happa-plugin-demo
cd happa-plugin-demo
npm init -y
npm install express

Step 3 - Create a working webhook server

This example verifies the HMAC signature before parsing JSON and responds quickly so Happa does not time out the delivery.

const express = require("express");
const crypto = require("crypto");

const app = express();
const WEBHOOK_SECRET = process.env.WEBHOOK_SECRET;

if (!WEBHOOK_SECRET) {
  throw new Error("WEBHOOK_SECRET is required");
}

app.get("/health", (_req, res) => {
  res.json({ ok: true });
});

app.use("/webhooks/happa", express.raw({ type: "application/json" }));

function verifySignature(rawBody, signatureHeader, secret) {
  const expected = "sha256=" +
    crypto.createHmac("sha256", secret).update(rawBody).digest("hex");

  try {
    return crypto.timingSafeEqual(
      Buffer.from(signatureHeader || "", "utf8"),
      Buffer.from(expected, "utf8")
    );
  } catch {
    return false;
  }
}

app.post("/webhooks/happa", (req, res) => {
  const signature = req.headers["x-happa-signature"];

  if (!verifySignature(req.body, signature, WEBHOOK_SECRET)) {
    return res.status(401).json({ error: "Invalid signature" });
  }

  const payload = JSON.parse(req.body.toString("utf8"));
  console.log("Received", payload.type, payload.data);

  res.status(200).json({ received: true });
});

app.listen(3000, () => {
  console.log("Listening on http://localhost:3000");
});

Step 4 - Run it behind a tunnel

WEBHOOK_SECRET=whsec_your_secret node index.js
# ngrok
ngrok http 3000

# or cloudflared
cloudflared tunnel --url http://localhost:3000

Update your plugin draft so the webhook URL points to the tunnel endpoint plus /webhooks/happa.

Step 5 - Send a test webhook

In the plugin detail screen, click Test Webhook. Your server should log something like this:

Received test.ping {
  message: "This is a test webhook from Happa. Your plugin is connected correctly.",
  pluginId: "plg_123"
}

You're connected. At this point your endpoint is verifying signatures correctly and can receive real deliveries.

Step 6 - Store installation tokens for API access

Webhook deliveries do not include the installation API token. That token is created when a user installs your plugin, and your own onboarding flow should send it to your backend for storage.

// Example endpoint in your own backend
app.post("/happa/install", express.json(), async (req, res) => {
  const { installationId, userId, apiToken, grantedScopes } = req.body;

  await saveInstallation({
    installationId,
    userId,
    apiToken,
    grantedScopes
  });

  res.json({ stored: true });
});

Step 7 - Call the REST API

const response = await fetch(
  "https://us-central1-happa-1aff4.cloudfunctions.net/pluginApi/events",
  {
    headers: {
      Authorization: `Bearer ${apiToken}`
    }
  }
);

const data = await response.json();
console.log(data.events);

Next steps