Agent skill

Smartsheet Webhooks Skill

Receive and verify Smartsheet webhooks. Use when setting up Smartsheet webhook handlers, answering the Smartsheet-Hook-Challenge verification handshake, debugging Smartsheet-Hmac-SHA256 signature verification, or handling sheet events like row.created, cell.updated, and sheet.updated.

Install this skill

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


When to Use This Skill

  • How do I receive Smartsheet webhooks?
  • How do I answer the Smartsheet-Hook-Challenge verification request?
  • How do I verify the Smartsheet-Hmac-SHA256 signature?
  • How do I handle row.created, cell.updated, or sheet.updated events?
  • Why was my Smartsheet webhook disabled (DISABLED_VERIFICATION_FAILED)?

Verification (core)

Smartsheet webhooks have two checks. Do the challenge first: Smartsheet sends a request with a Smartsheet-Hook-Challenge header when you enable the webhook and again about once every 100 callbacks. Answer it with a 200 that echoes the value in the Smartsheet-Hook-Response response header (or a JSON body {"smartsheetHookResponse": "<value>"}). Challenge requests may be unsigned, so do this before any signature check. If you fail it, Smartsheet disables the webhook (DISABLED_VERIFICATION_FAILED).

Callbacks carry Smartsheet-Hmac-SHA256: the hex HMAC-SHA256 of the raw body, keyed with the webhook's sharedSecret (assigned by Smartsheet on creation). Compare case-insensitively.

const crypto = require('crypto');

// 1. Handshake first (may be unsigned)
const challenge = req.get('Smartsheet-Hook-Challenge');
if (challenge) {
  res.set('Smartsheet-Hook-Response', challenge);
  return res.status(200).json({ smartsheetHookResponse: challenge });
}

// 2. Signature over the raw body (Buffer from express.raw({ type: '*/*' }))
function verify(rawBody, header, sharedSecret) {
  if (!header) return false;
  const expected = crypto.createHmac('sha256', sharedSecret).update(rawBody).digest('hex');
  const a = Buffer.from(header.trim().toLowerCase());
  const b = Buffer.from(expected);
  return a.length === b.length && crypto.timingSafeEqual(a, b);
}

Python:

import hmac, hashlib

def verify(raw_body: bytes, header: str, shared_secret: str) -> bool:
    expected = hmac.new(shared_secret.encode(), raw_body, hashlib.sha256).hexdigest()
    return hmac.compare_digest((header or "").strip().lower().encode(), expected.encode())

Smartsheet's SDKs have no webhook verification helper, so verification is manual in every example. There is no timestamp in the signed content.

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

Common Event Types

Each callback has scopeObjectId and an events array; every event has an objectType and an eventType. Callbacks are "skinny": fetch the object through the API for details.

Event (objectType.eventType)Triggered When
row.createdA row is added to the sheet
row.updatedA row is updated
row.deletedA row is deleted
cell.updatedA cell value changes
sheet.updatedThe sheet is updated

Other sheet objectType values: attachment, column, comment, discussion. A webhook can filter with patterns such as ["row.created", "cell.updated"] or row.*; the default is *.*. A status change callback (newWebhookStatus) is sent when a webhook is disabled or re-enabled because its scope became inaccessible or accessible.

See references/overview.md for the payload shape.

Environment Variables

# sharedSecret returned when you create the webhook (POST /webhooks)
SMARTSHEET_SHARED_SECRET=your_webhook_shared_secret

Local Development

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

Reference Materials


Repository

hookdeck/webhook-skills

v0.1.0 · MIT · Updated Nov 5, 2026

View on GitHub →