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 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.
- Create a webhook in the Swoogo UI, or with the API.
- Set the URL, the trigger object, and which of insert, update and delete should fire it.
- Narrow it with event IDs, changed fields or origin flags if you only need some changes.
- Choose the fields to send and the content type,
jsonorpost. - Add a custom header with your secret value, for example
Authorization: Bearer <your random value>. - 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:
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.
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:
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 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 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
jsonfor new webhooks. - Parse by
Content-Typeso either format works.
How Hookdeck Can Help: Hookdeck accepts both content types, and a transformation 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
2xxas 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 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 thepublic_user,admin_userandapi_callflags to fire only on changes you care about.
How Hookdeck Can Help: Hookdeck absorbs the burst and delivers at 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.
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.
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 checks the header, handles both content types, and keeps every change until your service has processed it.
Get started with Hookdeck for free and handle Swoogo webhooks reliably in minutes.