Skip to content
Pidgely Docs

Guides

Events

Send what your users do in your product (sign-ups, trials, payments, logins) to start, stop and personalize automations. Idempotent, on every plan.

View as Markdown

Events are the things your users do in your product: user_signed_up, trial_started, payment_failed, login. Send them to Pidgely with one request and they:

  • create or update the contact (name and properties), so your contacts stay current without a separate sync;
  • start automations with the custom event trigger, end them when a goal or exit event arrives, and resume wait for event steps;
  • feed the inactivity trigger (no login for 14 days);
  • are listed in the dashboard (Marketing → Events) per contact, live.

Events work on every plan. Automations need Scale, but the events you record before upgrading are kept, so contacts and activity are ready when you do.

Send an event

POST /events with a full_access or sending_access API key:

cURL
curl -X POST https://api.pidgely.com/events \
  -H "Authorization: Bearer $PIDGELY_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "trial_started",
    "email": "ana.souza@example.com",
    "contact": { "first_name": "Ana", "last_name": "Souza", "properties": { "plan": "pro", "trial_ends_at": "2026-11-05" } },
    "properties": { "plan": "pro", "trial_days": 14 },
    "idempotency_key": "trial-started-8812"
  }'
Node.js
const res = await fetch('https://api.pidgely.com/events', {
  method: 'POST',
  headers: { Authorization: `Bearer ${process.env.PIDGELY_API_KEY}`, 'Content-Type': 'application/json' },
  body: JSON.stringify({
    name: 'trial_started',
    email: 'ana.souza@example.com',
    contact: { first_name: 'Ana', last_name: 'Souza', properties: { plan: 'pro', trial_ends_at: '2026-11-05' } },
    properties: { plan: 'pro', trial_days: 14 },
    idempotency_key: 'trial-started-8812',
  }),
});
if (!res.ok) throw new Error(`pidgely: ${res.status} ${await res.text()}`);
const event = await res.json(); // { object: 'event', id, contact_id, duplicate, ... }
Python
import os
import requests

res = requests.post(
    "https://api.pidgely.com/events",
    headers={"Authorization": f"Bearer {os.environ['PIDGELY_API_KEY']}"},
    json={
        "name": "trial_started",
        "email": "ana.souza@example.com",
        "contact": {"first_name": "Ana", "last_name": "Souza", "properties": {"plan": "pro", "trial_ends_at": "2026-11-05"}},
        "properties": {"plan": "pro", "trial_days": 14},
        "idempotency_key": "trial-started-8812",
    },
    timeout=10,
)
res.raise_for_status()
event = res.json()
Go
package main

import (
	"bytes"
	"encoding/json"
	"fmt"
	"net/http"
	"os"
)

func main() {
	body, _ := json.Marshal(map[string]any{
		"name":  "trial_started",
		"email": "ana.souza@example.com",
		"contact": map[string]any{
			"first_name": "Ana",
			"last_name":  "Souza",
			"properties": map[string]any{"plan": "pro", "trial_ends_at": "2026-11-05"},
		},
		"properties":      map[string]any{"plan": "pro", "trial_days": 14},
		"idempotency_key": "trial-started-8812",
	})
	req, _ := http.NewRequest("POST", "https://api.pidgely.com/events", bytes.NewReader(body))
	req.Header.Set("Authorization", "Bearer "+os.Getenv("PIDGELY_API_KEY"))
	req.Header.Set("Content-Type", "application/json")
	res, err := http.DefaultClient.Do(req)
	if err != nil {
		panic(err)
	}
	defer res.Body.Close()
	var event map[string]any
	json.NewDecoder(res.Body).Decode(&event)
	fmt.Println(res.Status, event["id"])
}

The answer is the recorded event, with the contact it belongs to:

JSON
{
  "object": "event",
  "id": "0199e4a2-7b8c-7d9e-8f0a-1b2c3d4e5f61",
  "name": "trial_started",
  "email": "ana.souza@example.com",
  "contact_id": "0199d7f4-5d7b-7a2c-b4e1-9f3c2a6d8e05",
  "properties": { "plan": "pro", "trial_days": 14 },
  "occurred_at": "2026-10-22T12:00:00Z",
  "received_at": "2026-10-22T12:00:00.184Z",
  "duplicate": false
}

Send events from your backend, where the API key is safe; never from the browser or a mobile app.

Fields

Field Required
name yes 1 to 100 characters: letters, digits, _, ., : and -. Use a stable past-tense name per action (subscription_started, invoice.paid). Names are case-sensitive.
email yes The contact's address. The contact is created when it does not exist yet (source event).
contact no first_name, last_name and properties to set on the contact. Properties merge with the existing ones (null removes one) and follow the contact property rules. Dates (trial_ends_at, renewal_at, birthday) as YYYY-MM-DD or RFC 3339 drive the contact date trigger. Events never change consent: an unsubscribed contact stays unsubscribed.
properties no Data of this event, a flat JSON object of strings, numbers, booleans or null, up to 50 keys and 16 KB. Available in automation e-mails as {{{event.<key>}}}; a goal reads its revenue from one of them (amount, in major units: 49.90).
occurred_at no When it happened, RFC 3339 (default: now). At most 5 minutes in the future.
idempotency_key no Up to 256 characters. The same key within 30 days returns the first event with duplicate: true and records nothing new. The Idempotency-Key header works too.

Historic events

An event whose occurred_at is more than 24 hours old is recorded, updates the contact and its last activity (for the inactivity trigger), and can end flows through goals, but it starts no automation. Importing a year of history never mails anyone.

Send many events

POST /events/batch takes up to 100 events. Each is handled like a single event; invalid ones are reported by index in errors and the others are recorded:

Shell
curl -X POST https://api.pidgely.com/events/batch \
  -H "Authorization: Bearer $PIDGELY_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "events": [
        { "name": "login", "email": "ana.souza@example.com" },
        { "name": "payment_succeeded", "email": "bruno@example.com", "properties": { "amount": 49.9, "plan": "pro" } }
      ] }'
JSON
{ "data": [{ "index": 0, "id": "0199e4a2-…", "duplicate": false }, { "index": 1, "id": "0199e4a2-…", "duplicate": false }], "errors": [] }

Rate limits and retries

Event requests count toward your organization's API rate limit like any other request (a batch counts once). On 429, wait for Retry-After and retry with the same idempotency_key: a retried event is never recorded twice. For high volumes, buffer events and send batches of up to 100.

These are the names the SaaS presets expect; you can rename them in each automation.

Event When Useful properties
user_signed_up An account is created plan, source
activated The user reached the first value (first project, first send)
login Each sign-in (or once a day)
checkout_started The user opened your checkout or pricing page plan, checkout_url
subscription_started A paid subscription starts plan, amount, currency
payment_failed A renewal charge failed amount, payment_url
payment_succeeded A charge (or retry) succeeded amount

Keep trial_ends_at (and renewal_at) up to date as contact properties: send them in contact.properties with trial_started, or update the contact directly.

See your events

The dashboard's Marketing → Events page lists recent events (newest first, live), filters by contact and by name, shows the names your product has sent, and has the snippet above with your API base URL. Through the API: GET /events (email, name, cursor pagination) and GET /events/names, with a full_access key.

Events are kept as long as your e-mail metadata, then deleted month by month.