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-Challengeverification request? - How do I verify the
Smartsheet-Hmac-SHA256signature? - How do I handle
row.created,cell.updated, orsheet.updatedevents? - 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_).
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.created | A row is added to the sheet |
row.updated | A row is updated |
row.deleted | A row is deleted |
cell.updated | A cell value changes |
sheet.updated | The 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
- references/overview.md - Webhook concepts, events, payload shape, retries
- references/setup.md - Create and enable a webhook, get the shared secret
- references/verification.md - Challenge handshake, signature details and gotchas