Quickstart

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

Before you start: the Developer Portal does not host your plugin code. You will run your own backend locally or in the cloud, and Happa will send it signed webhooks.

Step 1 - Register your plugin

Open the Developer Portal. The landing page takes you into the portal first, and if you are signed out, the dashboard will prompt you to continue with Google before you create a plugin record for the backend you are about to run. For your first draft, use:

Save the draft and copy the generated webhookSecret. You will use it in your own backend in the next step.

If sign-in does not open: check that developers.happa.site is allowed in Firebase Authentication authorized domains and that your deployed portal uses that same domain as its Firebase auth domain.

Building a payment or finance plugin? Stop here and read Financial Plugins first. Those integrations use a stricter company-review flow and are not handled like ordinary self-serve plugins.

Step 2 - Install dependencies

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

Step 3 - Create your 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 public 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