# Tremendous Webhooks

## When to Use This Skill

* How do I receive Tremendous webhooks?
* How do I verify the `Tremendous-Webhook-Signature` header?
* Why is my Tremendous webhook signature verification failing?
* How do I handle `REWARDS.DELIVERY.SUCCEEDED` and `REWARDS.DELIVERY.FAILED` events?
* How do I get the webhook `private_key` for a Tremendous webhook?
* How do I handle `ORDERS.APPROVED`, `ORDERS.FAILED`, or `FUNDING_SOURCES.FUNDED` events?

## Verification (core)

Tremendous signs the **raw request body** with HMAC-SHA256, keyed with the webhook's `private_key`, and sends the hex digest in `Tremendous-Webhook-Signature` as `sha256=<hex>`. The prefix is always present: require it and strip it before comparing. There is no timestamp or message ID in the signed content. Read the raw body (any content type) and compare in constant time.

Node:

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

function verify(rawBody, signatureHeader, privateKey) {
  if (!signatureHeader || !signatureHeader.startsWith('sha256=')) return false;
  const sig = signatureHeader.slice('sha256='.length);
  // Check hex shape first: Buffer.from(x, 'hex') silently truncates invalid input
  if (!/^[0-9a-f]{64}$/i.test(sig)) return false;
  const expected = crypto.createHmac('sha256', privateKey).update(rawBody).digest();
  return crypto.timingSafeEqual(Buffer.from(sig, 'hex'), expected);
}

```

Python:

```python
import hmac, hashlib

def verify(raw_body: bytes, signature_header: str, private_key: str) -> bool:
    if not signature_header or not signature_header.startswith("sha256="):
        return False
    sig = signature_header[len("sha256="):].lower()
    expected = hmac.new(private_key.encode(), raw_body, hashlib.sha256).hexdigest()
    # Compare bytes so a non-ASCII header returns False instead of raising
    return hmac.compare_digest(sig.encode(), expected.encode())

```

In Express use `express.raw({ type: '*/*' })` on the route so verification works whatever `Content-Type` arrives. Tremendous has no SDK helper for webhook verification, so verification is manual in every framework.

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

## Common Event Types

Events are uppercase and dotted. The payload's `event` field carries the name:

| Event | Description |
| --- | --- |
| `REWARDS.DELIVERY.SUCCEEDED` | Tremendous received confirmation the reward email was delivered (not sent for LINK rewards) |
| `REWARDS.DELIVERY.FAILED` | Reward delivery failed, usually an email bounce (`payload.meta` has the failure details) |
| `REWARDS.CANCELED` | A reward was canceled |
| `REWARDS.FLAGGED` | A reward was flagged for fraud review |
| `ORDERS.CREATED` | An order was created (`payload.meta` holds the Order) |
| `ORDERS.APPROVED` | An admin approved an order (only fires when order approvals are on) |
| `ORDERS.CANCELED` | An order held for review was rejected |
| `ORDERS.FAILED` | An order failed, typically a declined card |
| `CAMPAIGNS.CREATED` | A campaign was created in the UI |
| `FUNDING_SOURCES.FUNDED` | Funds were added and made available on your balance |
| `INVOICES.PAID` | An invoice was paid |

Tremendous sends every event to the one receiver registered for your organization, so filter in your handler. The full list (36 events across campaigns, connected organizations, funding sources, invoices, members, orders, products, rewards, fraud reviews, reports and topups) is in [references/overview.md](https://github.com/hookdeck/webhook-skills/blob/main/skills/tremendous-webhooks/references/overview.md).

## Important Headers and Payload

| Item | Description |
| --- | --- |
| `Tremendous-Webhook-Signature` | `sha256=<hex>` HMAC-SHA256 of the raw body |
| `event` | Event name, e.g. `ORDERS.APPROVED` |
| `uuid` | Unique ID of this webhook; use it to dedupe retries |
| `payload.resource` | `{ id, type }` of the resource the event refers to |

## Environment Variables

```bash
TREMENDOUS_WEBHOOK_PRIVATE_KEY=your_webhook_private_key   # returned when you create or retrieve the webhook via the API

```

## Local Development

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

```

## Reference Materials

* [references/overview.md](https://github.com/hookdeck/webhook-skills/blob/main/skills/tremendous-webhooks/references/overview.md) - Tremendous webhook concepts, events, payloads, retries
* [references/setup.md](https://github.com/hookdeck/webhook-skills/blob/main/skills/tremendous-webhooks/references/setup.md) - Create the webhook via the API and get the private key
* [references/verification.md](https://github.com/hookdeck/webhook-skills/blob/main/skills/tremendous-webhooks/references/verification.md) - Signature verification details and gotchas