Agent skill
Pax8 Webhooks Skill
Receive and authenticate Pax8 webhooks. Use when setting up a Pax8 partner Webhooks API endpoint, checking the Authorization header Pax8 sends back, debugging 401s, or handling SUBSCRIPTION, INVOICE, CLIENT, PRODUCT, QUOTE and USER topic events such as SUBSCRIPTION CREATE.
Install this skill
npx skills add hookdeck/webhook-skills --skill pax8-webhooks
Pax8 (cloud marketplace for MSPs) sends webhooks from its partner Webhooks API when events happen on the platform, grouped by topic (for example SUBSCRIPTION, INVOICE) and action (for example CREATE). Payloads are not signed: the docs say HMAC is "not yet" supported. The only credential is the optional Authorization value you set on the webhook, which Pax8 sends back "exactly as you inputed".
Covers partner Webhooks API webhooks only. Paste the Authorization value exactly as set in Pax8, including any "Bearer " or "Basic " prefix.
(Vendor Provisioner webhooks are a different feature with a Pax8-generated credential in a vendor-chosen header; this skill does not cover them.)
When to Use This Skill
- How do I receive Pax8 webhooks?
- How do I verify the
Authorizationheader on a Pax8 webhook? - Does Pax8 sign webhook payloads? (No: static shared credential only)
- Why does my Pax8 webhook endpoint return 401?
- How do I handle
SUBSCRIPTION,INVOICEorCLIENTevents from Pax8?
Verification (core)
Set an authorization value when you create the webhook (Pax8 stores it encrypted). On every delivery Pax8 sends it back unchanged in the Authorization header. The key is the whole header value, including any Bearer or Basic prefix: do not strip, parse or base64-decode it.
const crypto = require('crypto');
// Hash both sides: equal-length buffers, so timingSafeEqual never throws.
function safeEqual(a, b) {
const ha = crypto.createHash('sha256').update(String(a)).digest();
const hb = crypto.createHash('sha256').update(String(b)).digest();
return crypto.timingSafeEqual(ha, hb);
}
function verifyPax8Request(headers, expected = process.env.PAX8_AUTHORIZATION) {
if (!expected) return false; // never accept when unconfigured
const received = headers['authorization']; // full value, e.g. "Bearer my_super_secret"
return Boolean(received) && safeEqual(received, expected);
}
Return 401 when this fails (missing header or mismatch), 400 for a body that isn't a JSON object, and 200 once handled. Read the body raw (express.raw({ type: '*/*' })) and parse it only after the check. Python and Next.js equivalents are in references/verification.md.
Security limits: a static shared credential authenticates the sender, not the body, and gives no replay protection (no signature, no timestamp). Use HTTPS only (Pax8 rejects non-https URLs) and rotate the credential if it leaks (update it with POST /webhooks/{id}/configuration).
For complete handlers with tests, see examples/express/, examples/nextjs/, examples/fastapi/.
Topics and Actions
Topics and actions are defined by GET /webhooks/topic-definitions, the authoritative catalog (create calls are rejected for anything not in it).
| Category | Example events (display names from the docs) |
|---|---|
| Subscription | Subscription Created, Updated, Cancelled, Commitment Term Updated, ... |
| Invoice | Invoice Created, Invoice Voided |
| Client | Client Created, Updated, Deleted |
| Product | Product Created, Updated, Deleted |
| Quote | Quote Pending, Created, Updated, Expired, Deleted, Line Items Added, ... |
| User | User Created, Deleted, Marked Active, Marked Inactive, Info Updated, ... |
The docs publish these as display names, not wire values. The only topic identifiers they show are SUBSCRIPTION and PRODUCT, and the only action is CREATE; the examples assume INVOICE, CLIENT, QUOTE and USER for the other categories. Check the topic-definitions API for the exact topics and actions on your account. The body is not documented (the samplePayload is elided), so the examples treat any payload as illustrative and read topic and action defensively.
Delivery Behavior
POSTwith a JSON body; can lag up to 5 minutes after the event- A response not received within 10 seconds counts as failed
- Up to 3 attempts (initial + 2 automatic retries), then log status
FAILED. The docs give the back-off as "10 s → 30 s → 60 s", three intervals for two retries, so the exact spacing is unclear - One email alert after
errorThresholdconsecutive failures (default 10, max 20) - Send a synthetic event with
POST /webhooks/{id}/topics/{topic}/test
Answer within 10 seconds and make handlers idempotent, as the docs give no event id.
Environment Variables
PAX8_AUTHORIZATION=Bearer my_super_secret # the full value you set as "authorization" in Pax8
Local Development
npx hookdeck-cli listen 3000 pax8 --path /webhooks/pax8
No account required. Use the printed URL as the webhook url.
Resources
- references/overview.md - Topics, actions, delivery behavior, illustrative payload
- references/setup.md - Creating a webhook and setting the authorization value
- references/verification.md - Header check in Node, Python and Next.js, gotchas
- examples/ - Runnable Express, Next.js and FastAPI handlers with tests