# Guide to Swoogo Webhooks: Features and Best Practices

Swoogo webhooks keep your other systems in step with your events. When someone registers, a registrant's details change, a session is added to the agenda, a speaker or sponsor is updated, or attendance is recorded, Swoogo can call your endpoint with the record's data. Event teams use this to sync registrations to a CRM or marketing platform, feed badge printing and access control, and update finance systems from registrant line items.

This guide covers how Swoogo webhooks are triggered and configured, the two payload formats, how to authenticate requests with a custom header, and the gaps in Swoogo's documentation that a receiver should allow for.

## What are Swoogo webhooks?

Each Swoogo webhook watches one kind of record, its **trigger object**, and fires when records of that kind are inserted, updated or deleted. You can narrow it to specific events, to changes in particular fields, and to changes made by public users, admins or API calls. Webhooks are organised into webhook groups, and you can manage them in the Swoogo UI or through the API.

You also choose what the request carries: Swoogo's default fields or the fields you select, sent as JSON or as a form-encoded body.

Swoogo doesn't sign webhooks. Its documentation describes custom headers "for authentication with your receiving endpoint": you add a header such as `Authorization` with a secret value, and your receiver compares it.

## Swoogo webhook features

| Feature | Details |
| --- | --- |
| Configuration | Swoogo UI, or `POST /webhooks/create` and related endpoints in the Swoogo API |
| Organisation | Webhook groups |
| Trigger objects | `event`, `contact`, `registrant`, `speaker`, `sponsor`, `session`, `session_attendance`, `registrant_line_item` |
| Operations | `trigger_on_insert`, `trigger_on_update`, `trigger_on_delete` |
| Origin filters | `public_user`, `admin_user`, `api_call` |
| Other filters | `event_ids`, changed `fields`, `all_fields`, `any_event` |
| Payload fields | `use_default_fields`, `post_field_data`, `custom_fields` |
| Format | `json` (`application/json`) or `post` (form-encoded) |
| Authentication | Custom headers (`headers` key/value pairs). No signature |
| Success | Any `2xx` |
| Retries | Failed deliveries are retried. Count, schedule and timeout not stated |
| Payload schema | Not documented |

## Triggers and payloads

| Trigger object | What it covers |
| --- | --- |
| `event` | The events themselves: conferences, meetings, webinars |
| `contact` | People in your Swoogo CRM, independent of any event |
| `registrant` | People registered for an event, with status, package, sessions and fees |
| `speaker` | Presenters linked to sessions |
| `sponsor` | Sponsoring organisations |
| `session` | Agenda items: talks, workshops, panels |
| `session_attendance` | Attendance records for sessions |
| `registrant_line_item` | Itemised fees, taxes and discounts on registrant orders |

Combine a trigger object with the insert, update and delete flags to get what other platforms would call event types, such as "registrant updated".

Swoogo doesn't publish a delivered webhook body, and doesn't document a field that names the trigger object or the operation. What arrives depends on the fields you choose. Two practical consequences:

* Give each webhook its own URL path, such as `/webhooks/swoogo/registrant`, so you know the trigger object before you parse anything.
* Capture one delivery per webhook before writing the handler, and include an ID field in what you send so you can deduplicate.

> **Inspect Swoogo webhooks as they arrive.** Point a webhook at a [Hookdeck Inspect](https://inspect.hookdeck.com) URL to see the exact body, content type and headers Swoogo sends for your field selection, with no account or setup required.

## Setting up Swoogo webhooks

Pick a header name, usually `Authorization`, and generate a long random value, for example with `openssl rand -hex 32`.

1. Create a webhook in the Swoogo UI, or with the API.
2. Set the **URL**, the **trigger object**, and which of insert, update and delete should fire it.
3. Narrow it with event IDs, changed fields or origin flags if you only need some changes.
4. Choose the fields to send and the content type, `json` or `post`.
5. Add a custom header with your secret value, for example `Authorization: Bearer <your random value>`.
6. Enable the webhook.

With the API, you get a bearer token through OAuth 2.0 client credentials at `POST /oauth2/token`, then call `POST https://api.swoogo.com/api/v1/webhooks/create` with a form-encoded body. Swoogo's own example:

```bash
curl -X POST "https://api.swoogo.com/api/v1/webhooks/create" \
  -H "Authorization: Bearer YOUR_ACCESS_TOKEN" \
  -H "Content-Type: application/x-www-form-urlencoded" \
  -d "name=Registrant Updates&webhook_group_id=1&trigger_object=registrant&trigger_on_insert=1&trigger_on_update=1&url=https://example.com/webhooks/registrant&content_type=json"

```

Custom headers are a `headers` array of `key` and `value` pairs on the webhook; Swoogo added API management of webhook headers in December 2024.

To route through Hookdeck, create a source with the **Swoogo** type and use its URL as the webhook URL. Enter the header name in **API Key Header** and the exact value, including any `Bearer ` prefix, in **API Key**. Hookdeck finds the header case-insensitively and compares the whole value. The source pre-selects `POST` but doesn't manage the method. Swoogo's create-webhook summary mentions an HTTP method, but its schema has no method field, so check the method on your first delivery and, if it isn't `POST`, allow that method on the source too.

## Securing Swoogo webhooks

Look up your header and compare its whole value with the stored secret, in constant time. Parse the body according to its content type, because a webhook can send JSON or form fields.

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

const HEADER_NAME = (process.env.SWOOGO_WEBHOOK_HEADER || 'authorization').toLowerCase();
const EXPECTED = process.env.SWOOGO_WEBHOOK_SECRET; // e.g. "Bearer 3f9c..."

function safeEqual(received, expected) {
  const a = Buffer.from(received, 'utf8');
  const b = Buffer.from(expected, 'utf8');
  // timingSafeEqual throws on unequal lengths, so check first.
  return a.length === b.length && crypto.timingSafeEqual(a, b);
}

function parseBody(req) {
  const text = req.body.toString('utf8');
  if ((req.get('content-type') || '').includes('application/x-www-form-urlencoded')) {
    return Object.fromEntries(new URLSearchParams(text));
  }
  return JSON.parse(text);
}

const app = express();

app.post('/webhooks/swoogo/:object', express.raw({ type: '*/*' }), (req, res) => {
  const received = req.get(HEADER_NAME);
  if (!EXPECTED || typeof received !== 'string' || !safeEqual(received, EXPECTED)) {
    return res.sendStatus(401);
  }

  let record;
  try {
    record = parseBody(req);
  } catch {
    return res.sendStatus(400);
  }

  // req.params.object tells you the trigger object. Queue the record and respond.
  res.sendStatus(200);
});

```

The same check in Python:

```python
import hmac
import os

HEADER_NAME = os.environ.get("SWOOGO_WEBHOOK_HEADER", "Authorization")
EXPECTED = os.environ.get("SWOOGO_WEBHOOK_SECRET", "")

def verify_swoogo_request(headers) -> bool:
    if not EXPECTED:
        return False  # never accept when unconfigured
    received = headers.get(HEADER_NAME)  # most frameworks look headers up case-insensitively
    if not received:
        return False
    # Compare bytes: compare_digest raises TypeError on a non-ASCII str.
    return hmac.compare_digest(received.encode("utf-8"), EXPECTED.encode("utf-8"))

```

A static header authenticates the sender, not the body. It cannot detect a modified payload, and with no signature or timestamp a captured request can be replayed and still pass. Registrant data is personal data, so serve the endpoint over HTTPS only, use a long random value, and update the header and your receiver together if it might have leaked.

> **Make Swoogo webhooks production-ready.** [Hookdeck Event Gateway](/event-gateway) checks your custom header, accepts JSON and form-encoded bodies alike, and queues every registration change durably for your service.

## Swoogo webhook limitations and pain points

### No documented payload or event field

**The Problem:** You can't tell from the request alone which kind of record changed, or whether it was inserted, updated or deleted.

**Why It Happens:** Swoogo documents how to configure webhooks but not the body it sends, which depends on the fields you choose.

**Workarounds:**

* Use one URL path per webhook so the path identifies the trigger object.
* Create separate webhooks for insert, update and delete if your handler needs to tell them apart.
* Capture a real delivery for each webhook and keep it as a test fixture.

**How Hookdeck Can Help:** Every request is stored with its full body and headers in Hookdeck, and [filters](/docs/filters) can route on the path or on fields you find in the body.

### Two content types

**The Problem:** A handler that assumes JSON fails on a webhook someone set to `post`.

**Why It Happens:** Each webhook chooses `json` or form-encoded delivery.

**Workarounds:**

* Standardise on `json` for new webhooks.
* Parse by `Content-Type` so either format works.

**How Hookdeck Can Help:** Hookdeck accepts both [content types](/docs/sources#content-types), and a [transformation](/docs/transformations) can turn form fields into JSON before delivery so your service handles one format.

### Retries without details

**The Problem:** You can't size your downtime tolerance or know when Swoogo gives up.

**Why It Happens:** Swoogo says a failed delivery is retried, but not how many times, how far apart, or what timeout it uses.

**Workarounds:**

* Respond with a `2xx` as soon as the record is stored.
* Reconcile registrations against the Swoogo API after incidents.

**How Hookdeck Can Help:** Hookdeck stores each request as it arrives and [retries](/docs/retries) delivery to your service on your schedule, and you can replay any request after a fix.

### Busy events, busy webhooks

**The Problem:** Registration opens, or an admin bulk-edits sessions, and your endpoint is flooded.

**Why It Happens:** Every insert or update on the trigger object fires the webhook, including changes made through the API.

**Workarounds:**

* Use `event_ids`, changed-field filters and the `public_user`, `admin_user` and `api_call` flags to fire only on changes you care about.

**How Hookdeck Can Help:** Hookdeck absorbs the burst and delivers at a [max delivery rate](/docs/destinations#set-a-max-delivery-rate) your service can handle. Hookdeck answers Swoogo with a `200` even when the header is wrong, but such requests are recorded as failed verification and never reach your service.

## Best practices

### One webhook, one path

Encode the trigger object, and the operation if you need it, in the URL path.

### Send what you need, including an ID

Choose fields deliberately and always include the record's ID, so you can deduplicate and fetch more from the API. See our [guide to webhook idempotency](/webhooks/guides/implement-webhook-idempotency).

### Prefer JSON

Use `content_type=json` unless a target can only take form posts.

### Avoid loops

If your receiver writes back to Swoogo through the API, turn off `api_call` on the webhook, or your own updates will fire it again.

### Acknowledge fast and process asynchronously

Store the record and respond, then sync other systems. See [why to process webhooks asynchronously](/webhooks/guides/why-implement-asynchronous-processing-webhooks).

## Conclusion

Swoogo webhooks fire on inserts, updates and deletes of one trigger object and send the fields you choose as JSON or form data, authenticated only by a custom header. Compare that header in constant time, give each webhook its own path, include an ID in the payload, and capture a real delivery before you build, because Swoogo documents neither the body nor its retry schedule.

[Hookdeck Event Gateway](https://hookdeck.com) checks the header, handles both content types, and keeps every change until your service has processed it.

[Get started with Hookdeck](https://dashboard.hookdeck.com/signup) for free and handle Swoogo webhooks reliably in minutes.