Agent skill

Commerce Layer Webhooks Skill

Receive and verify Commerce Layer webhooks. Use when setting up Commerce Layer webhook handlers, debugging X-CommerceLayer-Signature verification, or handling commerce events like orders.place, orders.approve, orders.pay, or shipments.ship.

Install this skill

npx skills add hookdeck/webhook-skills --skill commercelayer-webhooks


When to Use This Skill

  • How do I receive Commerce Layer webhooks?
  • How do I verify Commerce Layer webhook signatures?
  • How do I handle orders.place, orders.approve, or orders.pay events?
  • Why is my Commerce Layer X-CommerceLayer-Signature verification failing?
  • Setting up a Commerce Layer callback endpoint for order/shipment events

Verification (core)

Commerce Layer signs the raw request body with HMAC-SHA256 keyed on the webhook's shared_secret and sends the digest as base64 in the X-CommerceLayer-Signature header. The triggering topic is in X-CommerceLayer-Topic. The shared_secret is returned once, in the response when you create the webhook (POST /api/webhooks) — it is not the same as your API credentials.

Read the raw body, NOT the parsed one. Re-serializing parsed JSON changes bytes (key order, whitespace) and breaks the signature. Commerce Layer has no SDK verify helper, so verify manually (this matches the official docs example).

Node:

const crypto = require('crypto');

function verifyCommerceLayerSignature(rawBody, signature, sharedSecret) {
  if (!signature) return false;
  const expected = crypto
    .createHmac('sha256', sharedSecret)
    .update(rawBody) // rawBody is a Buffer/string — never JSON.parse first
    .digest('base64');
  try {
    return crypto.timingSafeEqual(Buffer.from(signature), Buffer.from(expected));
  } catch {
    return false; // length mismatch = invalid
  }
}

Python:

import hmac, hashlib, base64

def verify_commercelayer_signature(raw_body: bytes, signature: str, shared_secret: str) -> bool:
    if not signature:
        return False
    expected = base64.b64encode(
        hmac.new(shared_secret.encode(), raw_body, hashlib.sha256).digest()
    ).decode()
    return hmac.compare_digest(signature, expected)

For complete handlers with route wiring, event dispatch, and tests, see:

Common Event Types (Topics)

Topics use the format {resource}.{trigger}.

TopicTriggered When
orders.placeCustomer places an order
orders.approveOrder is approved
orders.cancelOrder is cancelled
orders.payOrder is paid (payment captured)
orders.refundOrder is refunded
customers.createA new customer is created
shipments.shipA shipment is shipped
shipments.deliverA shipment is delivered

Commerce Layer supports 100+ topics across orders, customers, shipments, returns, refunds, authorizations, captures, gift_cards, and more. For the full list, see references/overview.md and the Commerce Layer webhooks docs.

Payload: JSON:API format, identical to fetching the resource via the REST API — { "data": { "id", "type", "attributes", "relationships" } }. For .destroy topics only data.id is populated (other attributes are null).

Environment Variables

COMMERCELAYER_SHARED_SECRET=your_webhook_shared_secret   # returned when you create the webhook

Local Development

# Start tunnel (no account needed)
npx hookdeck-cli listen 3000 commercelayer --path /webhooks/commercelayer

Reliability & Retries

  • Your endpoint must return a 2xx status within 5 seconds.
  • Failed deliveries are retried up to 10 times.
  • After 5 unsuccessful attempts, the organization owner/admins are notified.
  • After 30 consecutive failures the webhook's circuit breaker trips (circuit_stateopen, tracked via circuit_failure_count) and it must be reset manually. (closed is the healthy default state.)

Verify fast, then do slow work asynchronously so you always answer within 5 seconds.

Reference Materials