Agent skill

Rutter Webhooks Skill

Receive and verify Rutter webhooks. Use when setting up Rutter (unified commerce and accounting API) webhook handlers, debugging X-Rutter-Signature verification, handling client secret rotation, or processing events like INITIAL_UPDATE, CONNECTION_NEEDS_UPDATE, ORDER_CREATED, or INVOICE_UPDATED.

Install this skill

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


When to Use This Skill

  • How do I receive Rutter webhooks?
  • How do I verify the X-Rutter-Signature header?
  • Why is my Rutter webhook signature verification failing?
  • How do I accept webhooks during a Rutter client secret rotation (X-Rutter-Signatures)?
  • How do I handle INITIAL_UPDATE, CONNECTION_NEEDS_UPDATE, ORDER_CREATED or INVOICE_UPDATED events?

Verification (core)

Rutter signs the raw request body with HMAC-SHA256, keyed with your organization's client secret (used as a raw string, not decoded). X-Rutter-Signature is sha256= + the base64 digest. X-Rutter-Signatures is a comma-separated list of every active signature: one entry normally, two during a client secret rotation (new secret first, expiring secret second). Accept the webhook if any signature you compute from any of your active secrets matches any entry in the list.

const crypto = require('crypto');

function verifyRutter(rawBody, signaturesHeader, secrets) {
  if (!signaturesHeader) return false;
  const received = signaturesHeader.split(',').map((s) => s.trim());
  return secrets.some((secret) => {
    const expected = 'sha256=' + crypto.createHmac('sha256', secret).update(rawBody).digest('base64');
    return received.some((sig) => {
      try {
        return crypto.timingSafeEqual(Buffer.from(sig), Buffer.from(expected));
      } catch {
        return false; // different lengths
      }
    });
  });
}

Python equivalent:

import base64, hashlib, hmac

def verify_rutter(raw_body: bytes, signatures_header: str, secrets: list[str]) -> bool:
    received = [s.strip() for s in (signatures_header or "").split(",") if s.strip()]
    for secret in secrets:
        digest = hmac.new(secret.encode(), raw_body, hashlib.sha256).digest()
        expected = "sha256=" + base64.b64encode(digest).decode()
        if any(hmac.compare_digest(sig.encode(), expected.encode()) for sig in received):
            return True
    return False

Rutter does not send a timestamp or webhook ID header, so there is no signed timestamp to check. Use the payload (type, code, entity id) for idempotency. Rutter has no official webhook verification SDK helper, so the examples verify manually.

For complete handlers with tests, see examples/express/, examples/nextjs/, examples/fastapi/.

Payload and Event Types

Every payload has a type (entity group, e.g. CONNECTION, ORDER) and a code (the specific event), plus access_token and connection_id. Entity events add the entity object under a lowercase key (order, invoice, ...). Route on code.

typecodeDescription
CONNECTIONINITIAL_UPDATEInitial data download finished; data can now be fetched
CONNECTIONCONNECTION_NEEDS_UPDATEMerchant unauthorized or auth failed; payload has update_url to re-authenticate
CONNECTIONCONNECTION_DISABLEDRutter disabled access (e.g. store suspended); has disable_error, disable_message
ORDERORDER_CREATED / ORDER_UPDATED / ORDER_DELETEDCommerce order changes
INVOICEINVOICE_CREATED / INVOICE_UPDATEDAccounting invoice changes

Other connection codes: HISTORICAL_UPDATE, INCREMENTAL_SYNC_COMPLETED, CONNECTION_UPDATED, CONNECTION_LINK_ERROR, CONNECTION_ERROR, CONNECTION_AUTHENTICATED. See references/overview.md for the full list.

Rutter must receive a 2xx within 5 seconds; failures are retried with exponential backoff for up to 15 minutes. The same payload can be delivered more than once, and a small share of webhooks can be dropped, so handlers must be idempotent and you should also poll the API.

Environment Variables

RUTTER_CLIENT_SECRET=your_client_secret            # Rutter dashboard, where you get your API keys
RUTTER_CLIENT_SECRET_PREVIOUS=                      # Optional: expiring secret during rotation

Local Development

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

Reference Materials


Repository

hookdeck/webhook-skills

v0.1.0 · MIT · Updated Nov 2, 2026

View on GitHub →