If you've built an inbound webhook with PX.Api.Webhooks.IWebhookHandler, you've hit the same wall we (Claude and I) did: the platform hands you the request and leaves authentication entirely to you. The official sample compares a bearer token with != — no HMAC, no timing safety, no replay protection, and a secret hardcoded next to it. Every team ends up re-implementing signature verification, and most of the common mistakes (verifying re-encoded text instead of raw bytes, short-circuiting comparisons, distinguishable error responses) are invisible until someone exploits them.
So we built it once, properly, and we're open-sourcing it under MIT:
https://github.com/AISI-Dev-Co/AISI.AcumaticaWebhookAuthenticator
A complete, authenticated GitHub webhook is this:
public class PushEventHandler : AuthenticatedWebhookHandlerBase{ protected override IWebhookAuthenticator CreateAuthenticator(IWebhookSecretProvider secrets) => new HmacAuthenticator(WebhookAuthPresets.GitHub(secrets)); protected override Task ProcessAsync(AuthenticatedWebhookContext context, CancellationToken cancellation) { // context.Body is the exact byte buffer the signature verified. }}The secret doesn't live in your code — an administrator maintains it on a Webhook Secrets screen (AS301000) that ships in the customization package, stored via [PXRSACryptString] per webhook registration. Edits go live within 30 seconds, no republish, and it works on SaaS.
What's in the box:
- Five schemes — HMAC, HMAC with a replay window, shared secret, HTTP Basic, and an explicit "none" (so no-auth is a written-down decision, not a forgotten gap). Presets for GitHub, Shopify and Stripe, and a signed-payload template language (
{timestamp}.{body},{header:Name}, …) for everything else — Stripe's compoundt=/v1=header and per-value timestamps included. - Zero-downtime secret rotation — old and new secrets both accepted until the overlap you set expires. No dropped requests mid-rotation, no maintenance window.
- Per-webhook IP allowlists (IPv4/IPv6 CIDR), admin-configurable on the same screen — for deployments behind a trusted proxy, as defence in depth on top of a signature.
- A signature debugger —
WebhookSignatureTestershows the exact string your configuration signed, the signatures it expected, and what the sender actually sent. It ends the hour every webhook integration starts with. - Security throughout — raw-bytes verification, constant-time comparison, fail-closed on missing secrets, and uniform 401s (diagnostic codes go to
PXTraceonly, never to the caller).
Getting started is four steps: import and publish AISI.WebhookAuthenticator.zip from the release (it creates the table, screen and access rights — no manual SQL), write a handler like the one above, register it on the Webhooks screen (SM304000), and enter the secret on AS301000.
Where we've used the patterns: Shopify orders → Sales Orders, Stripe payment events → AR, 3PL tracking callbacks, DocuSign envelope completion, and iPaaS tools (Boomi/Celigo/Zapier) that only offer a static header — which is exactly what the shared-secret scheme plus an IP allowlist is for.
Supports Acumatica 2025 R2 – 2026 R1. Issues, PRs and "sender X does something weird" reports very welcome — new schemes just need a known-good and known-bad vector.
Happy Auth’ing!