VYG Docs
Custom Events

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

  1. 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 type from the name you give it. That type is 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.
  2. 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>.
  3. VYG validates the payload against your schema, resolves the customer using your identifier mapping, and records the event.
  4. 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 webhook event_id identifies 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 with 422 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:

StatusReasonCauseShould you retry?
400invalid_payloadRequired 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.
404unknown_event_typeNo enabled event definition matches the requested type.No — register or re-enable the definition, then re-send.
422missing_external_idThe external_id_path list resolved to nothing for this payload.No — fix the payload or extend external_id_path.
422missing_contact_channelNeither 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.
422contact_channel_conflictA 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.

On this page