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:
- Which events Commerce Catalyst sends.
- How to set up an endpoint.
- What Commerce Catalyst sends to your endpoint.
- How to verify the signature.
- How to answer requests and handle retries and duplicates.
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. |
Set up an endpoint
In Commerce Catalyst
- Go to Integrations → Other → Webhooks.
- Click Add endpoint.
- Enter your URL. It must start with
https://and be reachable from the internet. - Optionally, enter a description.
- Copy the endpoint's signing secret (
whsec_…) and store it in your system.
From the same card you can:
- Send test: sends a
webhook.testevent 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) orapi(created through the self-service API).
"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:
- Compute the same HMAC over the raw body exactly as received (before parsing the JSON).
- Compare it with
v1in constant time. - Reject requests whose
tis 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
createdAtif order matters.
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.