Custom Events
Define webhook event types, validate payloads and use events in campaigns.
Define custom events such as abandoned checkouts, loyalty changes or survey responses. Each needs:
- A unique name, sent as
type. - A JSON Schema describing the payload.
- An identifier mapping that tells VYG which fields in your payload identify the customer.
Use these events to start campaigns and personalize messages. See Triggering flows.
How it works
- Set up the event in Settings → Data sources. Open the data source, click Add event,
paste an example event, confirm the event's name in your tool, name it and choose the customer
fields. VYG matches incoming events on the event's name in your tool, so your tool keeps sending
the events it already sends. VYG also generates a
typefrom the name you give it. Thattypeis the event's identifier in the VYG API and in flows, not a value your tool has to send. See Data sources in the app. You can also do this from your own code over the VYG API; see Managing your integration. - Send events to the webhook URL. A tool set up in the app sends its own event name. If you
send events from your own code in the VYG format instead, set
x-vyg-topic: <your-slug>. - VYG validates the payload against your schema, resolves the customer using your identifier mapping, and records the event.
- Build a flow on your event in the campaign builder. Once that flow is active, accepted events can trigger it after processing. See Triggering flows.
Slug rules
The type must match ^[a-z0-9_]{1,64}$:
- Lowercase ASCII letters, digits, and underscores.
- 1 to 64 characters.
- No slashes, hyphens, dots, or unicode.
Examples: loyalty_tier_changed, survey_completed, wishlist_add.
In the app, the type is set from the event's name when the event is created, for example
Loyalty tier changed becomes loyalty_tier_changed. It is shown read-only, with Copy, as
Event type, for API users on the event's More tab.
Payload schema
A definition's payload_schema is a standard
JSON Schema (draft-07) document. It
declares required payload fields and their expected types. Missing required fields
are rejected. Supply values that match your registered schema.
{
"$schema": "http://json-schema.org/draft-07/schema#",
"type": "object",
"required": ["customer_id", "tier"],
"properties": {
"customer_id": { "type": "string" },
"email": { "type": "string", "format": "email" },
"tier": { "type": "string", "enum": ["bronze", "silver", "gold"] },
"changed_at": { "type": "string", "format": "date-time" }
}
}Include all required fields in each delivery. Validate types, formats and extra fields in your sender as well; acceptance does not guarantee strict enforcement of every JSON Schema constraint.
Updating a definition's schema increments schema_version. Subsequent deliveries
are validated against the updated schema.
In the app, the schema's fields are on the event's Fields section, titled Fields this event must include. A field marked Must include is a required field. An event set up with the guided flow accepts any fields until you mark some of them there.
Identifier mapping
Use identifier_mappings to tell VYG which payload fields contain the entity ID and customer
contact details.
type CustomEventIdentifierMappings = {
external_id_path: string[]; // required, ≥1
email_path?: string[];
phone_path?: string[];
event_id_path?: string[];
name_path?: string[];
first_name_path?: string[];
last_name_path?: string[];
received_at_path?: string[];
};Each value is an ordered list of field paths in the payload. VYG uses the first
non-empty value
(null, undefined, and the empty string all skip).
Required fields
external_id_path— at least one path that always resolves. The resolved value is the stable ID of the entity represented by this event, such as a checkout or customer. Deliveries for that entity and event definition refer to the same saved record. The webhookevent_ididentifies the delivery and serves a different purpose.- At least one of
email_path/phone_path— without an email or phone, VYG has no channel to reach the shopper, and the event will be rejected with422 missing_contact_channel. Email alone is enough to accept the event and can trigger email flows. SMS and agent flows still need a phone number on the contact.
Example
For a payload like:
{
"customer_id": "cust_001",
"customer": { "email": "shopper@example.com" },
"contact": { "phone": "+15551234567" },
"meta": { "source_event": "evt_abc" }
}A reasonable mapping is:
{
"external_id_path": ["customer_id"],
"email_path": ["customer.email", "email"],
"phone_path": ["contact.phone", "customer.phone"],
"event_id_path": ["meta.source_event"]
}For example, ["customer.email", "email"] checks customer.email first, then email.
In the app
The event editor shows these mappings as rows with a field picker, grouped under Customer (email,
phone, order or record ID and names) and When it happened (the time, from received_at_path). A
row with several fields reads Use the first one that has a value and lists them in order. See
Field mappings.
Wire format
See
Webhook Endpoint Reference for headers
and authentication requirements. Set type and the matching x-vyg-topic header
to your registered slug.
type CustomWebhookBody = {
type: string; // your registered slug, e.g. "loyalty_tier_changed"
event_id: string; // matches the x-vyg-event-id header
data: Record<string, unknown>; // validated against your payload_schema
};data must be a JSON object. Arrays and scalars are rejected with
400 invalid_payload.
Sample request
Assuming a definition registered with slug loyalty_tier_changed and
the schema / mapping above:
SECRET="your-webhook-secret"
URL="https://<your-vyg-webhook-host>/custom/V1StGXR8_Z5jdHi6B-myT"
BODY='{"type":"loyalty_tier_changed","event_id":"evt_loyalty_1","data":{"customer_id":"cust_001","customer":{"email":"shopper@example.com"},"contact":{"phone":"+15551234567"},"tier":"gold","changed_at":"2026-05-08T10:15:00Z"}}'
SIG=$(printf '%s' "$BODY" | openssl dgst -sha256 -hmac "$SECRET" -hex | sed 's/^.* //')
curl -sS -X POST "$URL" \
-H "content-type: application/json" \
-H "x-vyg-topic: loyalty_tier_changed" \
-H "x-vyg-event-id: evt_loyalty_1" \
-H "x-vyg-signature: $SIG" \
--data "$BODY"A successful response includes the recorded event ID. flow_event_id is null;
the response does not confirm a campaign run or message delivery. See
Triggering flows for where to check campaign activity.
{
"ok": true,
"custom_event_id": "8e3b0f5a-...-...",
"flow_event_id": null
}A repeated delivery may return:
{ "deduplicated": true }Error codes
In addition to the standard webhook statuses, custom events surface a few event-specific reasons:
| Status | Reason | Cause | Should you retry? |
|---|---|---|---|
400 | invalid_payload | Required payload fields are missing, or data is not an object. details lists validation errors. | No — fix the payload and resend. A new event_id is not required. |
404 | unknown_event_type | No enabled event definition matches the requested type. | No — register or re-enable the definition, then re-send. |
422 | missing_external_id | The external_id_path list resolved to nothing for this payload. | No — fix the payload or extend external_id_path. |
422 | missing_contact_channel | Neither email_path nor phone_path resolved a value. Email alone is accepted; phone is only required for SMS/agent reachability. | No — include an email or phone, or extend the mapping. |
422 | contact_channel_conflict | A resolved email or phone is already owned by a different contact. details lists the conflicting channel usernames. | No — resolve the identity conflict (or use a different contact channel) before resending. |
After 404 unknown_event_type, register or enable the definition and resend
the delivery with the same event_id.
401 (HMAC) and 500 (transient) behave as documented in
Errors and retries.
Triggering flows
Build a campaign flow using your registered event and activate it before sending events intended to start that campaign. Event processing and campaign actions are asynchronous. A successful webhook response confirms that VYG accepted the event; it does not confirm that a message was sent.
Contact channel vs flow channel
Acceptance only requires email or phone. That is separate from which flows can run:
- Email-only contacts can trigger email flows.
- SMS and agent flows still need a phone channel on the contact.
To check the result:
- Open Settings → Data sources, select the data source and check its Activity tab for recent deliveries. Each event also has its own Activity tab.
- Open the campaign's activity in VYG to check flow runs and messages.
If custom event triggers are unavailable, contact your VYG account manager.
Repeated deliveries
Retry an unchanged delivery with the same event_id. The stable entity ID selected by
external_id_path identifies the saved record within the event definition. Another delivery
for that entity does not create a separate record. Use a different entity ID only for a
genuinely different entity. See Webhook retries.
Schema fields in the campaign builder
Your event fields are available in the campaign builder:
- Trigger selection. Your registered events appear by name in the trigger node's event menu.
- Node filters. Each node's filter picker offers your event's payload fields (all leaf and array fields), so you can branch on your own data, alongside the standard contact and brand facts.
- Message variables. Email, SMS, and agent message composers offer your
event's payload fields as
event.*variables, alongside the common brand, contact, and generated variables (including discount codes). This applies to AI-assisted message writing as well.
Use these fields to filter your audience and personalize messages.