Commerce Catalyst Webhooks

Commerce Catalyst can tell your own systems (for example an order management system or CRM) the moment something happens, by sending an HTTPS request to a URL you choose. This is a webhook. You will learn:

  1. Which events Commerce Catalyst sends.
  2. How to set up an endpoint.
  3. What Commerce Catalyst sends to your endpoint.
  4. How to verify the signature.
  5. How to answer requests and handle retries and duplicates.
You need the Manage Self-Service permission to set up webhooks.


Events

Event Sent when
portal_user.created A brand portal user is created: they sign up on your brand portal, an admin adds them in Commerce Catalyst (Brands → People), or your systems create them through the self-service API.
More events will be added over time. An endpoint only receives the events it is subscribed to.




Set up an endpoint

In Commerce Catalyst

  1. Go to Integrations → Other → Webhooks.
  2. Click Add endpoint.
  3. Enter your URL. It must start with https:// and be reachable from the internet.
  4. Optionally, enter a description.
  5. Copy the endpoint's signing secret (whsec_…) and store it in your system.
Commerce Catalyst shows the signing secret once. Copy it before you leave the page.

From the same card you can:

  • Send test: sends a webhook.test event straight away and shows whether your endpoint accepted it.
  • Turn off / Turn on: pauses or resumes an endpoint.
  • New secret: replaces the signing secret. The old one stops working at once.
  • Recent deliveries: open an endpoint to see its recent deliveries, and click Resend on any that failed.


With the API

You can do the same from your own systems with the self-service API (https://commercecatalyst.adbutler.com/api/v1/self-service), using the AdButler API key (Authorization: Basic ) or a Commerce Catalyst API key (Authorization: Bearer cc_…).

Method Path What it does
GET /webhooks List your endpoints
POST /webhooks Add one: {"url": "https://…", "description": "OMS"}. The answer includes the full secret, shown only this once
GET /webhooks/{id} Get one
PATCH /webhooks/{id} Change url, events, description or active
DELETE /webhooks/{id} Delete it
POST /webhooks/{id}/rotate-secret New secret (shown in the answer)
POST /webhooks/{id}/test Send a test event now
GET /webhooks/{id}/deliveries Latest deliveries, newest first
POST /webhooks/{id}/deliveries/{deliveryId}/redeliver Send one again now


Example

POST /api/v1/self-service/webhooks
Authorization: Basic 
Content-Type: application/json
{"url": "https://oms.example.com/cc-webhooks", "description": "Order management system"}

Response: 201 Created

{
  "id": 3,
  "url": "https://oms.example.com/cc-webhooks",
  "events": ["portal_user.created"],
  "description": "Order management system",
  "active": true,
  "secret": "whsec_6c1f…(48 characters)",
  "createdAt": "2026-11-05T16:26:02Z",
  "updatedAt": "2026-11-05T16:26:02Z"
}




What Commerce Catalyst sends

Commerce Catalyst sends a POST to your URL with a JSON body.

Headers

POST /cc-webhooks
Content-Type: application/json
User-Agent: CommerceCatalyst-Webhooks/1.0
X-CC-Event: portal_user.created
X-CC-Event-Id: 1b8f6a52-6f0e-4a8e-9c1d-3f2b7c9d0e11
X-CC-Delivery-Attempt: 1
X-CC-Signature: t=1793895964,v1=8f3a…(64 hex characters)


Body

{
  "id": "1b8f6a52-6f0e-4a8e-9c1d-3f2b7c9d0e11",
  "type": "portal_user.created",
  "createdAt": "2026-11-05T16:26:04Z",
  "organizationId": 10016,
  "data": {
    "source": "api",
    "user": {
      "id": 10045,
      "email": "buyer@supplier.example",
      "name": "Jamie Buyer",
      "status": "active",
      "externalId": "USR-1001",
      "hasPassword": false,
      "lastLoginAt": null,
      "createdAt": "2026-11-05T16:26:04Z",
      "updatedAt": "2026-11-05T16:26:04Z",
      "brands": [
        {"id": 10031, "name": "Supplier Co", "adbutlerAdvertiserId": 5574, "status": "approved", "externalId": "ACC-2001"}
      ]
    }
  }
}
  • data.user.id: the Commerce Catalyst user id (the same id the self-service API uses).
  • data.user.brands: the brands they can work on, each with its AdButler advertiser id.
  • data.source: how the user was created: signup (they signed up on the brand portal), admin (added in Commerce Catalyst) or api (created through the self-service API).
Someone who signs up on a portal where new brands need approval has a brand with "status": "pending" until it is approved.




Verify the signature

Every request is signed with your endpoint's secret, so you can be sure it came from Commerce Catalyst and wasn't changed. X-CC-Signature has two parts:

  • t: when it was sent (Unix time, seconds).
  • v1: the HMAC-SHA256 of "." with the secret, in hex.

To check it:

  1. Compute the same HMAC over the raw body exactly as received (before parsing the JSON).
  2. Compare it with v1 in constant time.
  3. Reject requests whose t is more than 5 minutes old.


Node.js

const crypto = require('crypto');
function isFromCommerceCatalyst(rawBody, signatureHeader, secret) {
  const parts = Object.fromEntries(signatureHeader.split(',').map((p) => p.split('=')));
  if (Math.abs(Date.now() / 1000 - Number(parts.t)) > 300) return false;
  const expected = crypto.createHmac('sha256', secret).update(`${parts.t}.${rawBody}`).digest('hex');
  return parts.v1?.length === expected.length && crypto.timingSafeEqual(Buffer.from(parts.v1), Buffer.from(expected));
}


PHP

function is_from_commerce_catalyst(string $rawBody, string $signatureHeader, string $secret): bool
{
    parse_str(str_replace(',', '&', $signatureHeader), $parts);
    if (abs(time() - (int) ($parts['t'] ?? 0)) > 300) {
        return false;
    }
    $expected = hash_hmac('sha256', ($parts['t'] ?? '') . '.' . $rawBody, $secret);
    return hash_equals($expected, (string) ($parts['v1'] ?? ''));
}




Answering, retries and duplicates

  • Answer quickly: reply with any 2xx status within 10 seconds. Do slow work (for example writing to your OMS) after answering.
  • Retries: anything else (another status, a timeout, a connection error) is retried after 1 minute, 5 minutes, 30 minutes, 2 hours, 6 hours and 12 hours. If every try fails, the delivery is marked failed; you can still resend it from Commerce Catalyst or with the API.
  • Redirects: not followed.
  • Order: events are usually sent within a few seconds. Order isn't guaranteed, so use createdAt if order matters.
The event id (and X-CC-Event-Id) is the same on every retry and resend. Store the ids you've processed and skip repeats.




This concludes the guide. With webhooks, your own systems can react the moment a brand portal user is created, without polling the self-service API.

We encourage you to read more of our documentation. You can also get in touch with our support team by opening a support ticket while you are logged in to your account.


Can't find what you're looking for?

Send us an email

hello@adbutler.com

Visit the blog

For more ad serving tips, industry news and AdButler insights.