Gareth Wilson Gareth Wilson

Guide to Pax8 Webhooks: Features and Best Practices

Published


Pax8 webhooks tell your systems when something changes on the Pax8 cloud marketplace: a subscription is created or cancelled, an invoice is issued or voided, a client is added, a quote changes or a user's account is updated. Managed service providers use them to keep PSA, billing and provisioning tools in step with Pax8 without polling its API.

This guide covers how partner webhooks are created through the Webhooks API, how to authenticate them, the topics and actions you can subscribe to, Pax8's delivery and retry rules, and the practices that make a Pax8 receiver dependable.

What are Pax8 webhooks?

Pax8 Event Subscriptions let a partner subscribe to events on the platform and receive each one as a JSON POST to a URL. You manage webhooks in the Pax8 app, on the Events tab under Integrations, or through the Webhooks API at https://api.pax8.com/api/v2/webhooks.

Each webhook subscribes to one or more topics, such as SUBSCRIPTION, and within each topic to actions, such as CREATE, optionally narrowed by conditions on fields like vendorId. Pax8 publishes the full catalog of topics, actions and filterable fields through GET /webhooks/topic-definitions, and rejects any webhook that references something not in it.

Pax8 doesn't sign webhooks. Asked whether it supports HMAC signatures, its FAQ answers "Not yet". Instead, a webhook can carry an optional authorization value, which Pax8 stores encrypted and sends back in the Authorization header "exactly as you inputed". Comparing that header with the value you set is how you authenticate a request.

This guide covers the partner Webhooks API. Pax8's Provisioner webhooks, which vendors configure for provisioning requests, use a Pax8-generated credential in a header that defaults to pax8ApiTokenV1, and are a separate feature.

Pax8 webhook features

FeatureDetails
ConfigurationThe Events tab in the Pax8 app, or POST /webhooks in the Webhooks API
Method and formatPOST with a JSON body
Endpointhttps:// URLs only
SubscriptionsTopics with action filters and conditions. One of each topic per webhook
CatalogGET /webhooks/topic-definitions
AuthenticationOptional authorization value, sent verbatim as Authorization
Signature and timestampNone
LatencyUp to 5 minutes after the event
Timeout10 seconds
RetriesUp to 3 attempts in total, back-off 10 s, 30 s, 60 s
Failure alertsOne email to contactEmail after errorThreshold consecutive failures (default 10, max 20)
Logs30 days, with manual retry
TestingPOST /webhooks/{id}/topics/{topic}/test
Source IPsNot published

Common events

Pax8 groups events into six categories. The public docs give display names, and the topic-definitions API gives the exact topic and action strings for your account.

CategoryExample events
ClientClient Created, Client Updated, Client Deleted
InvoiceInvoice Created, Invoice Voided
ProductProduct Created, Product Updated, Product Deleted
QuoteQuote Pending, Quote Created, Quote Updated, Quote Customer Responded, Quote Expired, Line Items Added
SubscriptionSubscription Created, Subscription Updated, Subscription Cancelled, Subscription Commitment Term Updated
UserUser Created, User Deleted, User Marked Active, User Info Updated, User Account Reassigned

Pax8 says its webhooks "are meant to be used as a notification system and not a full state transfer": treat each one as a prompt to call the relevant Pax8 API for the current state. The notification body isn't shown in the public docs. Each topic definition includes a samplePayload, and the test endpoint sends it to your URL, so use that to see the shape before you write a parser.

Event emission follows permissions. The token owner must have access to the account's data, and if the account loses permission to an object, such as a product removed from its catalog, no events are sent for it even though the webhook still exists.

Setting up Pax8 webhooks

Generate a long random value first, and decide the full string you want Pax8 to send, for example Bearer followed by the output of openssl rand -hex 32.

Create the webhook with a Pax8 OAuth token that has the webhook scopes:

curl -X POST "https://api.pax8.com/api/v2/webhooks" \
  -H "Authorization: Bearer $PAX8_ACCESS_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "displayName": "Subscription listener",
    "url": "https://example.com/webhooks/pax8",
    "authorization": "Bearer YOUR_LONG_RANDOM_VALUE",
    "contactEmail": "ops@example.com",
    "errorThreshold": 10,
    "active": true,
    "webhookTopics": [
      {
        "topic": "SUBSCRIPTION",
        "filters": [
          {
            "action": "CREATE",
            "conditions": [
              { "field": "vendorId", "operator": "IN", "value": ["d6489ab3-3e13-4fe1-84ac-08d4d8c4b802"] }
            ]
          }
        ]
      }
    ]
  }'

This is Pax8's own example shape, narrowed to subscriptions from one vendor. Check availableFilters in the topic definition for the actions and conditions each topic accepts. A webhook created without topics is set inactive. Change the URL or the authorization value later with POST /webhooks/{id}/configuration, and add topics with POST /webhooks/{id}/topics. Then send a test with POST /webhooks/{id}/topics/SUBSCRIPTION/test and check the delivery logs.

To route through Hookdeck, create a source with the Pax8 type and use its URL as the webhook url. Paste the authorization value into Authorization Header exactly as you set it in Pax8, including any Bearer or Basic prefix. The source compares the whole value and accepts POST.

Securing Pax8 webhooks

Compare the whole Authorization header with your stored value, in constant time. Don't strip a prefix or decode the value.

const crypto = require('crypto');
const express = require('express');

// Exactly what you set as "authorization" on the Pax8 webhook.
const EXPECTED = process.env.PAX8_AUTHORIZATION;

function safeEqual(received, expected) {
  const a = Buffer.from(received, 'utf8');
  const b = Buffer.from(expected, 'utf8');
  // timingSafeEqual throws on unequal lengths, so check first.
  return a.length === b.length && crypto.timingSafeEqual(a, b);
}

const app = express();

app.post('/webhooks/pax8', express.raw({ type: '*/*' }), (req, res) => {
  const received = req.get('authorization');
  if (!EXPECTED || typeof received !== 'string' || !safeEqual(received, EXPECTED)) {
    return res.sendStatus(401);
  }

  let notification;
  try {
    notification = JSON.parse(req.body.toString('utf8'));
  } catch {
    return res.sendStatus(400);
  }

  // Queue it and respond within 10 seconds. A worker fetches current state from the Pax8 API.
  res.sendStatus(200);
});

The same check in Python:

import hmac
import os

EXPECTED = os.environ.get("PAX8_AUTHORIZATION", "")


def verify_pax8_request(headers) -> bool:
    if not EXPECTED:
        return False  # fail closed when unconfigured
    received = headers.get("authorization")
    if not received:
        return False
    # Compare bytes: compare_digest raises TypeError on a non-ASCII str.
    return hmac.compare_digest(received.encode("utf-8"), EXPECTED.encode("utf-8"))

A static Authorization value authenticates the sender, not the body. It can't detect a modified payload, and with no signed timestamp a captured request can be replayed and still pass. Pax8 requires HTTPS, so use a long random value and replace it with the configuration endpoint if it might have leaked.

Make Pax8 webhooks production-ready. Hookdeck Event Gateway checks the Authorization value on every Pax8 notification, answers well inside the 10-second timeout, and keeps each event durably for replay.

Pax8 webhook limitations and pain points

Three attempts and done

The Problem: A few minutes of downtime loses subscription and billing events.

Why It Happens: Pax8 attempts each event at most 3 times, with back-off of 10, 30 and 60 seconds, then marks it FAILED. Setting errorThreshold below 3 caps the retries further. The email alert only fires after errorThreshold consecutive failures, and only if contactEmail is set.

Workarounds:

  • Set contactEmail, and keep errorThreshold at 3 or more.
  • After an outage, retry failed deliveries from the logs with POST /webhooks/{webhookId}/logs/{logId}/retry. Logs are kept for 30 days.

How Hookdeck Can Help: Hookdeck accepts each notification as soon as it arrives, so Pax8 records a success, then delivers to your service with retries on your schedule. Issues alert you to failing deliveries, and any event can be replayed.

Ten seconds to answer

The Problem: A handler that calls the Pax8 API or your PSA before responding times out, and the event uses up an attempt.

Why It Happens: Pax8 marks a call as failed if no response arrives within 10 seconds.

Workarounds:

  • Verify, queue and respond. Do the API calls in a worker.

How Hookdeck Can Help: Hookdeck responds to Pax8 immediately and delivers to your service with a delivery timeout you choose.

Notifications, not state

The Problem: The body doesn't contain everything you need, or describes a state that has already moved on.

Why It Happens: Pax8 designs webhooks as notifications and recommends calling its APIs for full state. Delivery can also lag the event by up to 5 minutes.

Workarounds:

  • Use the identifiers in the notification to fetch the current resource.
  • Apply changes based on what the API returns, not on arrival order.

How Hookdeck Can Help: Hookdeck stores every notification with its body and arrival time, so you can see exactly what Pax8 sent and when.

Authorization is optional

The Problem: Requests arrive with nothing to check.

Why It Happens: The authorization field is optional. Pax8 encourages you to set it, but doesn't require it.

Workarounds:

  • Always set it, and fail closed when the header is missing.

How Hookdeck Can Help: Hookdeck's Pax8 source requires the value. Hookdeck answers Pax8 with a 200 either way, but a request with a missing or wrong header is recorded as failed verification and never reaches your destination.

Best practices

Store the full header value

Keep the exact authorization string, prefix included, and compare it whole.

Drive your subscriptions from the catalog

Read topics, actions and filterable fields from GET /webhooks/topic-definitions rather than hard-coding them. Pax8 adds new topics there first, and create calls fail for anything not in it.

Deduplicate deliveries

Manual retries from the logs resend an event, so make processing idempotent. Pax8's public docs don't show an event ID, so check the sample payload for one, or build a key from the topic, action and resource ID. See our guide to webhook idempotency.

Acknowledge fast and process asynchronously

Ten seconds and three attempts leave little room. See why to process webhooks asynchronously.

Conclusion

Pax8 partner webhooks send a JSON POST for each subscribed topic and action, authenticated only by the optional authorization value returned in the Authorization header. Always set that value and compare it whole, drive subscriptions from the topic-definitions catalog, fetch current state from the API, and respond within 10 seconds, because each event gets only three attempts.

Hookdeck Event Gateway sits in front of your receiver, checks the header, answers Pax8 immediately, and keeps every event until your service has processed it.

Get started with Hookdeck for free and handle Pax8 webhooks reliably in minutes.


Gareth Wilson

Gareth Wilson

Product Marketing

Multi-time founding marketer, Gareth is PMM at Hookdeck and author of the newsletter, Community Inc.