Contact events (Preview)

Preview · Available to selected organizations. Record custom contact events, retry them safely, and read each contact's event timeline through the SDK, REST API, CLI, or MCP.

Send a custom event when a contact takes an action in your application. Identify the contact with exactly one of id, externalId, or email. An unmatched email or external ID creates a contact; an unknown contact ID returns 404.

Record an event

record-event.ts
import { createClient } from "samva";

const samva = createClient({ apiKey: process.env.SAMVA_API_KEY! });
const event = await samva.events.create({
  contact: { email: "ada@example.com" },
  name: "trial.started",
  properties: { plan: "starter" },
  idempotencyKey: "trial-started-customer-42",
});
console.log(event.id, event.contactId);
Record an event with REST
curl https://api.samva.dev/v1/events \
  -H "x-api-key: $SAMVA_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"contact":{"email":"ada@example.com"},"name":"trial.started","properties":{"plan":"starter"},"idempotencyKey":"trial-started-customer-42"}'

Reuse the same idempotencyKey when retrying. The key belongs to your organization; a replay returns the original event without updating it or creating another contact. Use a new key to record a different event.

occurredAt is an optional ISO 8601 instant for when the action happened. The response also includes recordedAt, when Samva recorded it, and source: api for your custom events or samva for contact lifecycle events. Event names beginning with contact. or samva. are reserved.

Record a batch

record-events.ts
import { createClient } from "samva";

const samva = createClient({ apiKey: process.env.SAMVA_API_KEY! });
const result = await samva.events.batch({
  events: [
    {
      contact: { externalId: "customer-42" },
      name: "trial.started",
      idempotencyKey: "trial-started-customer-42",
    },
  ],
});
for (const item of result.results) {
  if (item.status === "error") console.error(item.index, item.error);
}

Samva validates the whole batch request before processing it. A request that does not match the event schema is rejected without recording any events. For a valid request, each event reports its result independently in input order. Inspect results even when the HTTP request succeeds: one rejected event does not undo accepted events.

Read a contact's timeline

contact-timeline.ts
import { createClient } from "samva";

const samva = createClient({ apiKey: process.env.SAMVA_API_KEY! });
const page = await samva.contacts.listEvents({
  id: "contact_7q2xk9mvt4znw8rh",
  limit: "25",
});
console.log(page.items);
// Supply page.nextCursor as cursor to read the next page, when it is non-null.

The timeline includes custom events and contact lifecycle events. Events remain in the timeline for the contact-event retention window. The API reference documents validation and payload limits.

CLI and MCP

Record and inspect contact events
samva events send --data @event.json --dry-run --json
samva events send --data @event.json --json
cat event.json | samva events send --data - --json
samva contacts events contact_7q2xk9mvt4znw8rh --limit 25 --json
samva contacts events contact_7q2xk9mvt4znw8rh --all --jsonl

--dry-run validates the request shape offline without recording an event or creating a contact. The API validates property limits when you submit the event. The timeline command reads one bounded page by default; --all --jsonl streams every page.

Hosted MCP provides send_contact_event and list_contact_events. The list tool returns one bounded page of event summaries without properties and a cursor. Both tools use your credential's organization and API permissions.

Related documentation

On this page