Webhook Payload Schemas
Every webhook delivery from Vivreal follows a consistent JSON structure. This guide documents the general payload format and provides examples for key event types.
General Payload Structure
All webhook payloads share this top-level structure:
{
"id": "evt_abc123def456",
"event": "collection.object.created",
"timestamp": "2026-03-15T14:30:00.000Z",
"groupId": "grp_789xyz",
"data": {
// Event-specific data
}
}
| Field | Type | Description |
|---|---|---|
id | string | Unique event identifier. Use for deduplication. |
event | string | The event type (see Event Types). |
timestamp | string | ISO 8601 timestamp of when the event occurred. |
groupId | string | The group where the event originated. |
data | object | Event-specific payload. Structure varies by event type. |
HTTP Headers
Each webhook request includes these headers:
| Header | Description |
|---|---|
Content-Type | application/json |
x-vivreal-signature | HMAC-SHA256 hex digest of the raw request body |
x-vivreal-event | The event type (same as event in the body) |
x-vivreal-delivery-id | Unique delivery ID (differs from event ID on retries) |
Example Payloads
collection.object.created
Fired when a new object is added to a collection.
{
"id": "evt_co_created_001",
"event": "collection.object.created",
"timestamp": "2026-03-15T14:30:00.000Z",
"groupId": "grp_789xyz",
"data": {
"objectId": "obj_abc123",
"collectionId": "col_def456",
"collectionName": "Products",
"fields": {
"name": "Wireless Headphones",
"price": 79.99,
"currency": "USD"
},
"author": "user@example.com",
"publishDate": null,
"archived": false
}
}
collection.object.updated
Fired when an existing object is modified. Includes both the changed fields and a summary of what changed.
{
"id": "evt_co_updated_002",
"event": "collection.object.updated",
"timestamp": "2026-03-15T15:00:00.000Z",
"groupId": "grp_789xyz",
"data": {
"objectId": "obj_abc123",
"collectionId": "col_def456",
"collectionName": "Products",
"fields": {
"name": "Wireless Headphones Pro",
"price": 99.99,
"currency": "USD"
},
"changedFields": ["name", "price"],
"author": "user@example.com",
"version": 3
}
}
collection.object.deleted
{
"id": "evt_co_deleted_003",
"event": "collection.object.deleted",
"timestamp": "2026-03-15T15:30:00.000Z",
"groupId": "grp_789xyz",
"data": {
"objectId": "obj_abc123",
"collectionId": "col_def456",
"collectionName": "Products",
"deletedBy": "admin@example.com"
}
}
integration.connected
{
"id": "evt_int_connected_004",
"event": "integration.connected",
"timestamp": "2026-03-15T16:00:00.000Z",
"groupId": "grp_789xyz",
"data": {
"integrationId": "int_ghi789",
"provider": "stripe",
"authMethod": "api_key",
"connectedBy": "admin@example.com"
}
}
site.deployed
{
"id": "evt_site_deployed_005",
"event": "site.deployed",
"timestamp": "2026-03-15T17:00:00.000Z",
"groupId": "grp_789xyz",
"data": {
"siteId": "site_jkl012",
"siteName": "My Store",
"url": "https://mystore.vivreal.app",
"customDomain": "www.mystore.com",
"template": "ecommerce",
"deployDuration": 145
}
}
site.failed
{
"id": "evt_site_failed_006",
"event": "site.failed",
"timestamp": "2026-03-15T17:05:00.000Z",
"groupId": "grp_789xyz",
"data": {
"siteId": "site_jkl012",
"siteName": "My Store",
"error": "Build failed: missing required environment variable",
"stage": "build"
}
}
Signature Verification
Always verify the x-vivreal-signature header before trusting the payload. Here is a complete example in Node.js:
import crypto from 'crypto';
import express from 'express';
const app = express();
const WEBHOOK_SECRET = process.env.VIVREAL_WEBHOOK_SECRET;
app.post(
'/webhooks/vivreal',
express.raw({ type: 'application/json' }),
(req, res) => {
const signature = req.headers['x-vivreal-signature'];
const rawBody = req.body.toString('utf8');
// Compute expected signature
const expected = crypto
.createHmac('sha256', WEBHOOK_SECRET)
.update(rawBody)
.digest('hex');
// Constant-time comparison to prevent timing attacks
const isValid = crypto.timingSafeEqual(
Buffer.from(signature, 'utf8'),
Buffer.from(expected, 'utf8')
);
if (!isValid) {
console.error('Webhook signature verification failed');
return res.status(401).json({ error: 'Invalid signature' });
}
// Parse and process the event
const event = JSON.parse(rawBody);
console.log(`Received ${event.event} (${event.id})`);
// Acknowledge receipt immediately
res.status(200).json({ received: true });
// Process asynchronously if needed
processEventAsync(event).catch(console.error);
}
);
Use express.raw() for signature verification
You must access the raw request body (not the parsed JSON) to compute the signature correctly. Using express.json() middleware before verification will alter the body and cause signature mismatches.
Python Verification Example
import hmac
import hashlib
import json
def verify_signature(raw_body: bytes, signature: str, secret: str) -> bool:
expected = hmac.new(
secret.encode('utf-8'),
raw_body,
hashlib.sha256
).hexdigest()
return hmac.compare_digest(signature, expected)
Handling Large Payloads
Most webhook payloads are small (under 10 KB). However, events for objects with rich text or many fields can be larger. Your endpoint should accept payloads up to 256 KB. If a payload exceeds this limit, Vivreal truncates the data.fields object and includes a truncated: true flag, along with the objectId so you can fetch the full object via the API.