# Shortcut Webhooks

## When to Use This Skill

* How do I receive Shortcut (Clubhouse) webhooks?
* How do I verify the Shortcut `Payload-Signature` header?
* Why is my Shortcut webhook signature verification failing?
* How do I handle story create, story update or epic update events?
* Why are my Shortcut webhooks arriving without a signature?

## Verification (core)

Shortcut signs a webhook **only if you entered a secret** when creating it. The `Payload-Signature` header is the hex-encoded HMAC-SHA256 of the **raw request body** (UTF-8), keyed with that secret string. There is no timestamp and no prefix. Shortcut has no webhook verification SDK, so verify manually and compare timing-safe.

Node:

```javascript
const crypto = require('crypto');

function verify(rawBody, signatureHeader, secret) {
  if (!signatureHeader || !secret) return false;
  const expected = crypto.createHmac('sha256', secret).update(rawBody).digest('hex');
  try {
    return crypto.timingSafeEqual(Buffer.from(signatureHeader.trim().toLowerCase()), Buffer.from(expected));
  } catch {
    return false; // different lengths
  }
}

```

Python:

```python
import hmac, hashlib

def verify(raw_body: bytes, signature_header: str, secret: str) -> bool:
    if not signature_header or not secret:
        return False
    expected = hmac.new(secret.encode(), raw_body, hashlib.sha256).hexdigest()
    return hmac.compare_digest(signature_header.strip().lower().encode(), expected.encode())

```

In Express, read the body with `express.raw({ type: '*/*' })` so any content type is verified against the exact bytes received.

> **For complete handlers with tests**, see [examples/express/](https://github.com/hookdeck/webhook-skills/tree/main/skills/shortcut-webhooks/examples/express/), [examples/nextjs/](https://github.com/hookdeck/webhook-skills/tree/main/skills/shortcut-webhooks/examples/nextjs/), [examples/fastapi/](https://github.com/hookdeck/webhook-skills/tree/main/skills/shortcut-webhooks/examples/fastapi/).

## Payload and Event Dispatch

Each delivery is one JSON event: `{ id, changed_at, primary_id?, member_id, version, actions: [...], references: [...] }`. Shortcut has no event-type header. Dispatch on each item in `actions[]` by `entity_type` and `action`; one delivery can hold several actions.

| entity_type + action | Description |
| --- | --- |
| `story create` | A story was created |
| `story update` | A story changed (see `changes`, e.g. `workflow_state_id`, `owner_ids`) |
| `story delete` | A story was deleted |
| `epic update` | An epic changed |
| `story-comment create` | A comment was added |

Shortcut fires for story and epic create, update and delete, including their comments and tasks. See [references/overview.md](https://github.com/hookdeck/webhook-skills/blob/main/skills/shortcut-webhooks/references/overview.md) for details.

## Environment Variables

```bash
SHORTCUT_WEBHOOK_SECRET=your_webhook_secret   # The secret you entered when creating the webhook

```

## Local Development

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

```

## Reference Materials

* [references/overview.md](https://github.com/hookdeck/webhook-skills/blob/main/skills/shortcut-webhooks/references/overview.md) - Shortcut webhook concepts, payload structure, events
* [references/setup.md](https://github.com/hookdeck/webhook-skills/blob/main/skills/shortcut-webhooks/references/setup.md) - Creating the webhook and setting a secret
* [references/verification.md](https://github.com/hookdeck/webhook-skills/blob/main/skills/shortcut-webhooks/references/verification.md) - Signature verification details and gotchas