Custom Events
Emit custom events from your application to trigger workflows, track user behavior, and resume waiting automation steps.
Emit events from your application to trigger workflows, track contact activity, and resume waiting automation steps.
Custom events let you send signals from your application to the Wraps platform. When an event is received, three things happen:
The event is recorded on the contact's timeline with a 2-year TTL.
Any enabled workflow with an event.received trigger matching this event name starts a new execution.
Any workflow execution waiting for this event on this contact resumes down the “yes” branch.
Use client.track() from the @wraps.dev/client SDK to send events with full type safety.
import { createPlatformClient } from '@wraps.dev/client';const client = createPlatformClient({ apiKey: process.env.WRAPS_API_KEY,});const result = await client.track('purchase.completed', { contactEmail: 'alice@example.com', properties: { orderId: 'ord_12345', amount: 99.00, plan: 'pro', },});// { success: true, contactCreated: false, workflowsTriggered: 1, executionsResumed: 0 }| Field | Type | Required | Description |
|---|---|---|---|
| name | string | Yes | First argument to track(). Event name, e.g. purchase.completed. Max 255 chars. |
| contactId | string | One of contactId or contactEmail | Contact ID to associate the event with. |
| contactEmail | string | Alternative to contactId | Look up the contact by email address. |
| contactName | string | No | Sets firstName when createIfMissing creates a new contact. |
| createIfMissing | boolean | No | If true and the contact doesn't exist, create one from contactEmail. Defaults to false. |
| properties | object | No | Arbitrary key-value data attached to the event. Passed to workflows as event data. |
Set createIfMissing: true to create a new contact when one doesn't exist. Requires contactEmail. The response includes contactCreated: true when a contact was created.
await client.track('signup.completed', { contactEmail: 'new-user@example.com', contactName: 'Alice', createIfMissing: true, properties: { source: 'website', referrer: 'producthunt', },});Tip: This is useful for signup flows where the contact may not exist in Wraps yet. The event fires, the contact is created, and any matching workflows start immediately.
Send multiple events in a single request with client.trackBatch(). Contact resolution, workflow matching, and execution resumption are all batched for efficiency.
const result = await client.trackBatch([ { name: 'page.viewed', contactEmail: 'alice@example.com', properties: { page: '/pricing' }, }, { name: 'page.viewed', contactEmail: 'bob@example.com', properties: { page: '/docs' }, }, { name: 'feature.used', contactId: 'con_abc123', properties: { feature: 'api-keys' }, },]);// { success: true, processed: 3, workflowsTriggered: 0, executionsResumed: 0, errors: [] }Note: Batch does not support createIfMissing. Contacts must already exist when using the batch endpoint.
Events integrate with workflows in two ways:
Create a workflow with the event.received trigger type and set the event name to match. When the event fires for a contact, a new workflow execution begins.
The event's properties are passed as event data and available in workflow step conditions.
Workflows can include a “Wait for Event” step that pauses execution until a specific event is received for that contact. When the matching event arrives, execution resumes down the “yes” branch. If the timeout expires first, it takes the “no” branch.
Tracked events are metered per organization per month with a 25% grace period.
| Plan | Monthly limit | Hard cap (125%) |
|---|---|---|
| Starter | 50,000 | 62,500 |
| Growth | 250,000 | 312,500 |
| Scale | 1,000,000 | 1,250,000 |
| Enterprise | Unlimited | — |
Emit an event after processing a payment to kick off a post-purchase workflow.
// app/api/checkout/route.tsimport { createPlatformClient } from '@wraps.dev/client';import { NextResponse } from 'next/server';const client = createPlatformClient({ apiKey: process.env.WRAPS_API_KEY,});export async function POST(request: Request) { const { email, plan, orderId } = await request.json(); // ... process payment ... // Emit event to trigger post-purchase workflow await client.track('purchase.completed', { contactEmail: email, createIfMissing: true, properties: { plan, orderId }, }); return NextResponse.json({ success: true });}Forward Stripe subscription events to Wraps for lifecycle automation.
// app/api/stripe-webhook/route.tsimport { createPlatformClient } from '@wraps.dev/client';import { NextResponse } from 'next/server';const client = createPlatformClient({ apiKey: process.env.WRAPS_API_KEY,});export async function POST(request: Request) { const event = await request.json(); switch (event.type) { case 'customer.subscription.created': await client.track('subscription.created', { contactEmail: event.data.object.customer_email, createIfMissing: true, properties: { plan: event.data.object.items.data[0].price.id, stripeCustomerId: event.data.object.customer, }, }); break; case 'customer.subscription.deleted': await client.track('subscription.cancelled', { contactEmail: event.data.object.customer_email, properties: { cancelReason: event.data.object.cancellation_details?.reason, }, }); break; } return NextResponse.json({ received: true });}Use dot-separated resource.action names. Keep them lowercase and consistent across your application.
| Event | Use case |
|---|---|
| signup.completed | User finished onboarding |
| purchase.completed | Order placed successfully |
| subscription.cancelled | User cancelled their plan |
| feature.used | Specific feature was activated |
| trial.expiring | Trial ends within 3 days |
| invoice.paid | Payment processed successfully |
Create automation workflows that trigger on your custom events.
Workflow guideReceive real-time delivery events at your HTTPS endpoint.
Webhooks guide