Webhooks
Happa delivers real-time notifications to your plugin as signed POST requests. Verify the signature on the raw request body before you trust or parse the payload.
Delivery contract
| Property | Value |
|---|---|
| Method | POST |
| Content type | application/json |
| Signature header | X-Happa-Signature: sha256=<hex> |
| Event header | X-Happa-Event: rsvp.created |
| User agent | Happa-Webhooks/1.0 |
| Timeout | Production deliveries time out after roughly 8 seconds. |
Payload envelope
{
"id": "evt_1749254400_k3j9m2",
"type": "rsvp.created",
"timestamp": "2026-06-07T14:30:00.000Z",
"data": {
"eventId": "abc123",
"eventTitle": "Summer Block Party",
"userId": "user456",
"joinMode": "open"
}
}
Node.js verification example
const express = require("express");
const crypto = require("crypto");
const app = express();
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, process.env.WEBHOOK_SECRET)) {
return res.status(401).send("Invalid signature");
}
const payload = JSON.parse(req.body.toString("utf8"));
queueWebhookForAsyncProcessing(payload);
res.sendStatus(200);
});
Python verification example
import hashlib
import hmac
from flask import Flask, request
app = Flask(__name__)
WEBHOOK_SECRET = "whsec_..."
def verify_signature(raw_body: bytes, signature_header: str, secret: str) -> bool:
expected = "sha256=" + hmac.new(
secret.encode("utf-8"),
raw_body,
hashlib.sha256,
).hexdigest()
return hmac.compare_digest(signature_header or "", expected)
@app.post("/webhooks/happa")
def happa_webhook():
if not verify_signature(request.get_data(), request.headers.get("X-Happa-Signature"), WEBHOOK_SECRET):
return "Invalid signature", 401
payload = request.get_json(force=True)
print(payload["type"], payload["data"])
return "ok", 200
Operational guidance
- Respond with a
2xxstatus as fast as possible and do heavy work asynchronously. - Use the webhook
idas your idempotency key so duplicate deliveries do not re-run side effects. - Log the
X-Happa-Eventheader and the payloadidtogether to simplify debugging. - Never disable signature verification in production, even for internal tools.
Failure handling
Failed deliveries appear in the Developer Portal's webhook logs with the HTTP status code and the first part of your response body. Happa does not currently retry failed deliveries automatically, so fix the issue and trigger another test.ping or wait for the next real event.
Payload reference: Use the Event Types reference for the exact fields Happa sends today, including ticket.sold and join-request events.