# Swoogo Webhooks

Swoogo (event management) sends an HTTP callback when a resource is inserted, updated or deleted.
Each webhook watches one `trigger_object` (`event`, `contact`, `registrant`, `speaker`, `sponsor`,
`session`, `session_attendance` or `registrant_line_item`) and can send `json` or form-encoded
(`post`) payloads. Webhooks are **not signed**. You authenticate them with a secret header that you
add to the webhook yourself (`headers: [{key, value}]`) and check on receipt.

## When to Use This Skill

* How do I receive Swoogo webhooks?
* Does Swoogo sign webhook payloads? (No: use a custom secret header)
* How do I add an `Authorization` or other secret header to a Swoogo webhook?
* How do I handle registrant insert or update webhooks from Swoogo?
* Why does my Swoogo webhook endpoint return 401?

## Verification (core)

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

// Hash both sides: equal-length digests, so timingSafeEqual never throws.
function safeEqual(a, b) {
  const ha = crypto.createHash('sha256').update(String(a)).digest();
  const hb = crypto.createHash('sha256').update(String(b)).digest();
  return crypto.timingSafeEqual(ha, hb);
}

function verifySwoogoRequest(headers,
    name = process.env.SWOOGO_WEBHOOK_HEADER || 'Authorization',
    secret = process.env.SWOOGO_WEBHOOK_SECRET) {
  if (!secret) return false;                      // never accept when unconfigured
  const received = headers[name.toLowerCase()];   // Node lowercases header names
  return Boolean(received) && safeEqual(received, secret); // WHOLE value, no 'Bearer ' parsing
}

```

* The header name is whatever you typed into the webhook's `headers`. Read it from
  `SWOOGO_WEBHOOK_HEADER` (default `Authorization`) and look it up case-insensitively.
* `SWOOGO_WEBHOOK_SECRET` is the **exact, whole header value**, including any `Bearer ` prefix you
  typed. Compare the whole value; don't strip or parse a prefix.
* Missing header or mismatch: return `401`. A malformed body after a good credential: `400`.
  Success: `200`.
* Read the body raw (`express.raw({ type: '*/*' })`) and authenticate before parsing it. The body
  is `application/json` or form-encoded depending on the webhook's `content_type`; accept both.

> **A static shared credential authenticates the sender, not the body, and gives no replay
> protection** (no signature, no timestamp). Use HTTPS only, and rotate the header value in Swoogo
> and in `SWOOGO_WEBHOOK_SECRET` if it leaks. Make handlers idempotent.

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

## Triggers and Payloads

Swoogo documents no event-type string or delivered example payload: the body carries the fields
you chose (`post_field_data`, `use_default_fields`), so the payloads in this skill are
**illustrative**. A webhook fires on the actions you enable (`trigger_on_insert`,
`trigger_on_update`, `trigger_on_delete`) for its one `trigger_object`.

| `trigger_object` | Fires on changes to |
| --- | --- |
| `event` | Events |
| `contact` | Contacts |
| `registrant` | Registrants |
| `speaker` | Speakers |
| `sponsor` | Sponsors |
| `session` | Sessions |
| `session_attendance` | Session attendance (check-ins) |
| `registrant_line_item` | Registrant line items |

Because the object isn't named in the body, the examples use a convention of this skill: create one
Swoogo webhook per object and put it in the URL, e.g. `https://example.com/webhooks/swoogo?object=registrant`.
Failed deliveries (non-2xx) are retried, so return `2xx` once handled.

## Environment Variables

```bash
SWOOGO_WEBHOOK_HEADER=Authorization       # name of the header you added to the webhook
SWOOGO_WEBHOOK_SECRET=Bearer your-secret  # exact whole header value

```

## Local Development

Swoogo can't reach `localhost`. Tunnel with the Hookdeck CLI (no account required):

```bash
npx hookdeck-cli listen 3000 swoogo --path /webhooks/swoogo

```

Use the printed URL as the webhook `url` in Swoogo.

## Resources

* [references/overview.md](https://github.com/hookdeck/webhook-skills/blob/main/skills/swoogo-webhooks/references/overview.md) - What Swoogo webhooks are, trigger objects and payload formats
* [references/setup.md](https://github.com/hookdeck/webhook-skills/blob/main/skills/swoogo-webhooks/references/setup.md) - Create a webhook group and webhook with a secret header
* [references/verification.md](https://github.com/hookdeck/webhook-skills/blob/main/skills/swoogo-webhooks/references/verification.md) - Header check details, limits, debugging 401s
* [examples/](https://github.com/hookdeck/webhook-skills/tree/main/skills/swoogo-webhooks/examples/) - Runnable Express, Next.js and FastAPI handlers with tests