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.
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
loginfor 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 -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"
}'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, ... }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()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:
{
"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:
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" } }
] }'{ "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.
Recommended events for SaaS
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.