This guide walks through creating a webhook subscribed to two ticket events, sending a signed test delivery, and verifying the X-Alga-Signature header on your receiver. By the end you have a working end-to-end loop you can plug into your own systems.
Start with two ticket events: ticket.created and ticket.assigned. You can subscribe to all six ticket events later — see the Webhooks reference for the full list.
curl -X GET "https://algapsa.com/api/v1/webhooks/events" \
-H "X-API-Key: $ALGA_API_KEY"Point the webhook at a URL on your own system that can receive POST requests over HTTPS. If you do not have a receiver yet, point at a service like webhook.site for the first test, then update the URL once your real receiver is ready.
curl -X POST "https://algapsa.com/api/v1/webhooks" \
-H "Content-Type: application/json" \
-H "X-API-Key: $ALGA_API_KEY" \
-d '{
"name": "Ticket subscriber",
"url": "https://your-app.example.com/webhooks/alga",
"event_types": ["ticket.created", "ticket.assigned"],
"is_active": true,
"verify_ssl": true
}'The response body includes a one-time signing_secret. Copy it into your secret manager immediately — AlgaPSA will not show it again. If you miss it, rotate with POST /api/v1/webhooks/{id}/secret/rotate.
ALGA_WEBHOOK_SECRET="kQqB...base64url..."Your receiver should read the raw request body before parsing JSON, verify the X-Alga-Signature header against ALGA_WEBHOOK_SECRET, and use X-Alga-Event-Id as the idempotency key. Reject anything that fails verification with a 401.
import { createHmac, timingSafeEqual } from "node:crypto";
import express from "express";
const app = express();
const seen = new Set<string>(); // replace with a real store
app.post(
"/webhooks/alga",
express.raw({ type: "application/json" }),
(req, res) => {
const sig = req.header("X-Alga-Signature") ?? "";
if (!verify(req.body.toString("utf8"), sig, process.env.ALGA_WEBHOOK_SECRET!)) {
return res.status(401).send("bad signature");
}
const eventId = req.header("X-Alga-Event-Id");
if (!eventId || seen.has(eventId)) return res.status(200).send("ok"); // dedupe
seen.add(eventId);
const envelope = JSON.parse(req.body.toString("utf8"));
handle(envelope.event_type, envelope.data);
res.status(200).send("ok");
},
);
function verify(body: string, header: string, secret: string): boolean {
const parts = Object.fromEntries(
header.split(",").map((p) => p.split("=") as [string, string]),
);
const t = Number(parts.t);
if (!t || Math.abs(Math.floor(Date.now() / 1000) - t) > 300) return false;
const expected = createHmac("sha256", secret).update(`${t}.${body}`).digest("hex");
const a = Buffer.from(expected, "hex");
const b = Buffer.from(parts.v1 ?? "", "hex");
return a.length === b.length && timingSafeEqual(a, b);
}
function handle(eventType: string, data: Record<string, unknown>): void {
// your business logic here
}The per-webhook test endpoint sends a signed envelope using the live secret. Use it to smoke-test your verification, idempotency, and handler code without waiting for a real ticket event.
curl -X POST "https://algapsa.com/api/v1/webhooks/$WEBHOOK_ID/test" \
-H "Content-Type: application/json" \
-H "X-API-Key: $ALGA_API_KEY" \
-d '{ "test_event_type": "ticket.assigned" }'The delivery shows up in the per-webhook history with is_test=true, a recorded response status, and the request/response bodies. Use this to confirm AlgaPSA reached you and saw your 200 OK.
curl -X GET "https://algapsa.com/api/v1/webhooks/$WEBHOOK_ID/deliveries?limit=5" \
-H "X-API-Key: $ALGA_API_KEY"Create or assign a ticket in AlgaPSA. Within a few seconds your receiver should see a real delivery with is_test absent and an event_id you have not seen before. From here you can expand event_types to cover more of the ticket lifecycle.