API Reference: Webhooks
Subscribe to event notifications via webhook subscriptions, verify signatures, and inspect delivery logs.
Overview
Webhooks notify your application when matching record events occur in Simply360. Register an HTTPS target URL, choose an event type, and Simply360 sends a signed HTTP POST request to that URL whenever a matching event is processed. Generic webhook management requires the TEAM_SETTINGS feature permission on your API key or token. Action Tags lifecycle webhooks are intentionally excluded from these routes and must use /v1/action-tags/webhooks, which enforces the Action Tags feature permission and signed-webhooks plan capability.
Key Concepts
- Subscription — A webhook subscription registers one target URL for one event type. The target must be a public HTTPS URL.
- Filter criteria — Optional JSON used to scope deliveries. For record events, use
filterCriteria.dataCollectionIdswith DataCollectionsimplyIdvalues. - Delivery — Every matching event triggers an HTTP POST with a JSON body. Attempts are retried on failure (default: 3 retries, 60 seconds apart, 30-second timeout — all configurable per subscription) and every attempt is logged.
- HMAC signature — Deliveries include a signature header,
X-S360-Signatureby default. Verify it against the raw request body before trusting the payload.
Available Event Types
| Event | Triggered When |
|---|---|
DATA_RECORD_CREATED | A data record is created. |
DATA_RECORD_UPDATED | A data record is updated. |
DATA_RECORD_DELETED | A data record is deleted. |
DATA_RECORDS_BULK_SYNCED | A bulk create, update, delete, or sync operation completes and emits one summary event. |
All four event types support the same collection filter shape:
{
"dataCollectionIds": ["XXXX-XXXX-XXXX"]
}
Despite the legacy key name dataCollectionIds, the values are DataCollection simplyId strings. Callers with full data access can create unfiltered record webhooks; other callers must filter to collections they can read.
Create a Webhook
Register a public HTTPS target URL for one event type. Simply360 rejects localhost, private network, link-local, internal, and reserved IP targets, including hostnames that resolve to those ranges. The response includes secret, the per-webhook signing secret. Store it immediately; it is returned only at creation time. Optional body properties retryCount, retryDelaySeconds, and timeoutSeconds tune delivery behavior.
TypeScript SDK
const webhook = await s360.webhooks.create({
eventType: 'DATA_RECORD_CREATED',
targetUrl: 'https://example.com/webhooks/simply360',
filterCriteria: {
dataCollectionIds: ['XXXX-XXXX-XXXX'],
},
});
console.log(`Webhook ID: ${webhook.data.id}`);
console.log(`Secret: ${webhook.data.secret}`); // store securely
cURL
curl -s -X POST "https://api.simply360.app/v1/webhooks" \
-H "Authorization: Bearer $S360_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"eventType": "DATA_RECORD_CREATED",
"targetUrl": "https://example.com/webhooks/simply360",
"filterCriteria": {
"dataCollectionIds": ["XXXX-XXXX-XXXX"]
}
}'
List, Update, Delete
PUT accepts targetUrl, filterCriteria, isActive, isPaused, retryCount, retryDelaySeconds, and timeoutSeconds.
await s360.webhooks.list();
await s360.webhooks.get('WHKS-1234-ABCD');
await s360.webhooks.update('WHKS-1234-ABCD', {
targetUrl: 'https://example.com/webhooks/simply360/v2',
});
await s360.webhooks.delete('WHKS-1234-ABCD');
Verify Webhook Signatures
Always verify the X-S360-Signature header before trusting a webhook payload. The signature is the hex-encoded HMAC-SHA-256 of the raw request body using the secret returned at creation.
import crypto from 'crypto';
function verifyWebhook(rawBody: string, signature: string, secret: string): boolean {
if (!/^[0-9a-f]{64}$/i.test(signature)) return false;
const expected = crypto.createHmac('sha256', secret).update(rawBody).digest('hex');
return crypto.timingSafeEqual(Buffer.from(signature, 'hex'), Buffer.from(expected, 'hex'));
}
app.post('/webhooks/simply360', express.raw({ type: 'application/json' }), (req, res) => {
const signature = req.header('x-s360-signature') ?? '';
const rawBody = (req.body as Buffer).toString('utf8');
if (!verifyWebhook(rawBody, signature, process.env.S360_WEBHOOK_SECRET!)) {
return res.status(401).send('Invalid signature');
}
const payload = JSON.parse(rawBody);
console.log(`Received ${payload.eventType}:`, payload.data);
res.status(200).send('OK');
});
Delivery Headers
| Header | Description |
|---|---|
Content-Type | application/json |
X-Simply360-Event-Id | Unique delivery event ID. Use this for idempotency. |
X-Simply360-Event-Type | Same value as eventType in the body. |
X-Simply360-Timestamp | ISO 8601 delivery timestamp. |
X-S360-Signature | Default signature header. Some legacy subscriptions may use a custom signature header configured on the subscription. |
Delivery Logs
GET /v1/webhooks/{webhookSimplyId}/deliveries lists delivery attempts (newest first, limit/offset paginated). Each entry includes eventType, deliveryStatus, attemptNumber, responseStatus, responseTimeMs, errorMessage, and timing fields. Stored response body diagnostics are capped at 4,096 characters. The subscription itself tracks totalDeliveries, successfulDeliveries, failedDeliveries, and lastDeliveryStatus.
const deliveries = await s360.webhooks.listDeliveries('WHKS-1234-ABCD');
for (const delivery of deliveries.data) {
console.log(`${delivery.eventOccurredAt}: ${delivery.deliveryStatus} (HTTP ${delivery.responseStatus})`);
}
curl -s "https://api.simply360.app/v1/webhooks/WHKS-1234-ABCD/deliveries" \
-H "Authorization: Bearer $S360_API_KEY"
Usage Notes
- Your endpoint must respond with a
2xxstatus. Non-2xx responses or network failures are treated as failed deliveries and retried per the subscription's retry settings. - Simply360 does not follow redirects when delivering webhooks. A
3xxresponse is recorded as a failed delivery; update the subscription'stargetUrlto the final endpoint instead. - Use the complete
X-Simply360-Event-Idor bodyeventIdas an idempotency key. Most events use a CHAR-14 ID and retain the general at-least-once delivery contract.WIZARD_COMPLETEDuses the stable keywizard:<dataWizardInstanceSimplyId>:completion:v1and the narrower durable-claim contract below. - A Wizard-completion claim performs the subscription's bounded HTTP retries in one owned delivery attempt. Queue redelivery cannot start another attempt after a claim exists. An ambiguous durable
pendingclaim is not retried automatically, because the receiving endpoint may already have accepted it; reconcile that case using the stable event ID before initiating any manual delivery. - Pause or re-enable a webhook by setting
isPaused/isActivewithPUT /v1/webhooks/{webhookSimplyId}. - Store the webhook
secretsecurely. If lost, delete and recreate the webhook. - See Webhook Event Payloads for the delivery body schema.