Contact events
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
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);curl https://api.samva.dev/v1/events \
-H "x-api-key: $SAMVA_API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: trial-started-customer-42" \
-d '{"contact":{"email":"ada@example.com"},"name":"trial.started","properties":{"plan":"starter"}}'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
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-batch-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. A batch uses one Idempotency-Key header. Keep the key and entry order stable on retries so each position replays its original event.
Read a contact's timeline
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
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 resources
- List a contact's events
Read the API reference for paging through one contact's event timeline.