Polar, Stripe, and GitHub speak their own JSON. AlertHub speaks ah_title, ah_level, and ah_key. Hookdeck Event Gateway sits in the middle: it acknowledges the provider instantly, verifies the signature, optionally rewrites the payload, and POSTs a clean alert to AlertHub. You do not run a server.
They are not alternatives. Hookdeck is webhook plumbing (queue, HMAC, retries, inspect/replay). AlertHub is the human layer: a searchable dashboard plus Slack, Telegram, ntfy, Gotify, email, Discord, Teams. Change chat tools in Connections; Polar keeps talking to Hookdeck.
Polar → Hookdeck (verify → filter → transform) → AlertHub ingest → dashboard + channels
Hookdeck’s free Developer plan includes 10,000 events/month. That is more than enough for AlertHub Pro (1,000 alerts/month).
Why put Hookdeck in front of AlertHub
Pointing Polar straight at https://api.thealerthub.com/sendAlert works if you stuff placeholders into the query string — same trick as Stripe and GitHub. Three things break down as soon as the payload is rich:
| Gap | Native URL to AlertHub | With Hookdeck |
|---|---|---|
| HMAC / Standard Webhooks | ingest does not verify Polar’s signature | Source verifies webhook-id / webhook-timestamp / webhook-signature |
| Archive vs rename | Polar has no product.archived event — archive is product.updated + data.is_archived | Transformation sets critical vs warning |
| Secret in Polar’s dashboard | ah_key in the endpoint URL | ah_key lives in a Hookdeck transformation variable |
| Polar is down / you are debugging | Polar retries blindly | Queue, Issues, bulk replay |
Use Hookdeck when the source is a SaaS webhook (Polar, Stripe, Shopify). Skip it for cron, bash, n8n, or the Ingestion API you already control.
How the pieces connect
- Polar POSTs its native event to a Hookdeck Source URL. Polar gets 200 immediately.
- A Filter (optional) drops everything that is not
product.updated. - A Transformation maps the Polar product to AlertHub fields and injects
ah_keyfromprocess.env. - The Destination is
POST https://api.thealerthub.com/sendAlert. - AlertHub authenticates the project, stores the row, and
handle-new-alertfans out. Only warning and critical leave the dashboard; info is history only. See alert levels.
Rule order: Filter then Transform. Hookdeck runs the filter after the transform by default. Drag Filter above Transform, or the filter will look for body.type on a body that already only has ah_*.
Setup (once)
1. AlertHub project
In the app, open the project and copy the secret key (ah_key). Connect Slack, Telegram, ntfy, or Gotify under Connections so the first real event has somewhere to go.
2. Hookdeck Source + Destination
In the Hookdeck dashboard:
- Create a Source. Name it
polar. Copy the Source URL (https://hkdk.events/…). - Create a Destination. URL
https://api.thealerthub.com/sendAlert, method POST. Do not putah_keyin this URL. - Create a Connection from that Source to that Destination.
Authentication on the Source: Polar signs with Standard Webhooks. In Polar you can set your own secret when creating the endpoint — generate one, paste it in Polar and in the Hookdeck Source verification field. If Polar generates the secret after save, copy it back into Hookdeck before you rely on production traffic.
3. Polar webhook
In Polar: Settings → Webhooks → Add endpoint.
- URL: the Hookdeck Source URL (not AlertHub).
- Format: Raw.
- Events:
product.updated(addproduct.createdif you also want new catalog items). Polar does not emitproduct.archived.
4. Filter (optional)
On the connection, add a Filter rule above Transform:
{
"type": "product.updated"
}
Only archives (skip name/price edits):
{
"type": "product.updated",
"data": { "is_archived": true }
}
Retries toward AlertHub: retry 5xx and timeouts. Do not retry 401 (bad ah_key) or 400 (missing title). A 429 is the monthly AlertHub quota — hammering ingest will not help until the next cycle.
Store ah_key as a Hookdeck variable
Do not paste the AlertHub project key into Polar, into the Destination URL, or into the transformation source as a string literal. Hookdeck transformations support environment variables for secrets.
- Open the connection → Transform → Create new transformation (or Editor on an existing one).
- In the transformation editor, open the Variables dropdown (top right of the code pane).
- Add a variable named
AH_KEY. Paste the AlertHub secret key as the value. Save. - In the JavaScript, read it with
process.env.AH_KEY— never interpolate it intorequest.queryor Polar’s endpoint URL.
The variable is scoped to that transformation. Anyone who can edit the transformation can see it; Polar’s webhook UI cannot. If you rotate the AlertHub key in the project, update only AH_KEY in Hookdeck — Polar’s endpoint URL stays the same.
Test payloads in the editor’s Input tab do not automatically inject env vars into a screenshot you share. After Run, confirm Output contains ah_key and that you are not committing the Output tab to git.
Copy-paste: Polar product.updated → AlertHub
Paste this as the transformation handler. It treats an archived product as critical and any other update as warning.
addHandler("transform", (request) => {
const event =
typeof request.body === "string" ? JSON.parse(request.body) : request.body;
const product = event.data || {};
const archived = Boolean(product.is_archived);
const price = (product.prices && product.prices[0]) || {};
const priceLabel =
price.amount_type === "free"
? "free"
: price.price_amount != null
? `${(price.price_amount / 100).toFixed(2)} ${(price.price_currency || "").toUpperCase()}`
: "n/a";
request.body = {
ah_key: process.env.AH_KEY,
ah_source: "Polar",
ah_level: archived ? "critical" : "warning",
ah_title: archived
? `Polar: product archived — ${product.name || product.id}`
: `Polar: product updated — ${product.name || product.id}`,
ah_description: archived
? `${product.name} (${product.id}) is no longer for sale. Existing subscriptions keep their benefits.`
: `ID: ${product.id}\nVisibility: ${product.visibility}\nRecurring: ${product.is_recurring} (${product.recurring_interval || "one-time"})\nPrice: ${priceLabel}`,
};
request.headers["content-type"] = "application/json";
delete request.headers["webhook-signature"];
delete request.headers["webhook-id"];
delete request.headers["webhook-timestamp"];
return request;
});
Drop Polar’s signature headers after rewriting the body: they no longer match, and AlertHub does not verify them.
Sample input (paste into Hookdeck Input → Run)
{
"type": "product.updated",
"timestamp": "2026-09-01T10:12:00.000Z",
"data": {
"id": "prod_01ABC",
"name": "Pro monthly",
"is_archived": false,
"is_recurring": true,
"recurring_interval": "month",
"visibility": "public",
"prices": [
{
"amount_type": "fixed",
"price_amount": 1000,
"price_currency": "eur"
}
]
}
}
Expected output body: ah_source: Polar, ah_level: warning, title containing Pro monthly, ah_key from the variable. Flip is_archived to true and Run again — level should become critical.
Name the transformation (e.g. polar-product-to-alerthub), Confirm, then Save the connection.
What you should see
Archive or rename a product in Polar (sandbox is enough). Hookdeck Requests shows the inbound Polar JSON; Events shows the transformed POST. AlertHub’s live feed gets the row. Connected channels receive warning / critical only.
One Hookdeck Source can fan out to several Destinations. Keep a second connection without this transform if another system still needs Polar’s original signed body.
Ideas to squeeze more value
- Same pattern for Stripe / GitHub / Shopify. New Source per provider, same AlertHub Destination, a transform that maps
type→ah_title/ah_level. Native placeholder URLs stay fine for trivial events — webhook builder. subscription.canceledat warning,order.paidat info. Info never pages; it still sits in the dashboard.- ntfy or Gotify for on-call, Slack for the team. Add both in Connections — hybrid on-call. Polar does not need a Discord/Slack format; leave Polar on Raw.
- Hookdeck Issues → AlertHub. When Hookdeck cannot deliver to ingest, that Issue can be a second connection that pages you. Meta-alerting for the plumbing.
FAQ
Does Polar POST to Slack?
No. Polar → Hookdeck → AlertHub → your Connections.
Can I skip the transformation?
Yes: Destination URL with query params (ah_key, ah_title={{type}}) like the Stripe guide. You lose archive-vs-update levels and you put ah_key in a URL. Fine for a smoke test, not for catalog changes.
Will Hookdeck retry AlertHub 429s all month?
Configure 4xx as non-retryable except maybe a short delay on 429 if you expect a quota bump. Do not use the default “retry for a week” on 401.
Does this replace Polar’s own retry log?
Hookdeck ACKs Polar quickly, so Polar’s retries should stay quiet. Debugging happens in Hookdeck (replay) and in AlertHub (history).
Next steps
Create the Source, paste its URL in Polar for product.updated, add AH_KEY, paste the transformation, send a test from Polar sandbox.
Related: Ingestion API · Stripe webhooks · GitHub webhooks · ntfy destination