Eventos
Envie o que acontece no seu produto para iniciar automações e personalizar e-mails.
Eventos contam à Postal o que acontece no seu produto: um teste grátis que começou, um pedido pago, um carrinho abandonado. Cada evento pode estar ligado a um contato e dispara as automações com o gatilho daquele nome. Para enviar eventos, basta uma chave só de envio, a mesma do seu backend; para consultar, uma de acesso total. Os eventos ficam guardados por 90 dias.
#Enviar um evento
POST/events
Registra o evento e, se ele tem contato, inicia as automações ativas cujo gatilho é este evento (e cujos filtros batem com as propriedades). Em contact, um e-mail que ainda não está na audiência cria o contato, o que dispara o webhook contact.created e as automações de “contato criado”. Sem contact, o evento fica registrado, mas não inicia automações.
Aceita Idempotency-Key: repetir a requisição com a mesma chave em até 24 horas devolve a mesma resposta, sem registrar o evento de novo nem iniciar outra execução.
Cabeçalhos
Idempotency-KeystringIdentificador único do evento no seu sistema, até 256 caracteres, comotrial-8812. Recomendado.
Corpo da requisição
namestringobrigatórioNome do evento, até 100 caracteres: letras, números,.,_,:e-, começando com letra ou número. Exemplos:trial.started,order_paid,checkout:abandoned.contactstringID ou e-mail do contato. Um e-mail desconhecido cria o contato; um ID desconhecido gera404.propertiesobjectDados do evento, em JSON, até 16 KB. Ficam disponíveis nos filtros e condições das automações e como variáveis nos e-mails ({{event.plan}},{{event.items.0.name}}).occurred_atstringQuando aconteceu, em ISO 8601. Padrão: agora. Não pode passar de 24 horas no futuro.
Erros
- 422
validation_errorNome ausente ou inválido,propertiesque não é objeto ou passa de 16 KB,contactque não é ID nem e-mail, ouoccurred_atno futuro. - 404
not_foundNenhum contato com o ID decontact. - 403
plan_limit_reachedO contato seria criado, mas o plano não permite mais contatos. - 409
invalid_idempotent_requestA mesmaIdempotency-Keyfoi usada com outro corpo.
cURL
curl -X POST https://api.postal.so/events \
-H "Authorization: Bearer $POSTAL_API_KEY" \
-H "Idempotency-Key: trial-8812" \
-H "Content-Type: application/json" \
-d '{
"name": "trial.started",
"contact": "[email protected]",
"properties": {
"plan": "pro",
"source": "website",
"trial_days": 14
}
}'
Node.js
const response = await fetch('https://api.postal.so/events', {
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.POSTAL_API_KEY}`,
'Content-Type': 'application/json',
'Idempotency-Key': 'trial-8812',
},
body: JSON.stringify({
name: 'trial.started',
contact: '[email protected]',
properties: {
plan: 'pro',
source: 'website',
trial_days: 14,
},
}),
});
const data = await response.json();
Python
import os
import requests
response = requests.post(
"https://api.postal.so/events",
headers={
"Authorization": f"Bearer {os.environ['POSTAL_API_KEY']}",
"Idempotency-Key": "trial-8812",
},
json={
"name": "trial.started",
"contact": "[email protected]",
"properties": {
"plan": "pro",
"source": "website",
"trial_days": 14,
},
},
timeout=10,
)
data = response.json()
PHP
$client = new \GuzzleHttp\Client(['base_uri' => 'https://api.postal.so']);
$response = $client->post('events', [
'headers' => [
'Authorization' => 'Bearer '.getenv('POSTAL_API_KEY'),
'Idempotency-Key' => 'trial-8812',
],
'json' => [
'name' => 'trial.started',
'contact' => '[email protected]',
'properties' => [
'plan' => 'pro',
'source' => 'website',
'trial_days' => 14,
],
],
]);
$data = json_decode($response->getBody(), true);
{
"object": "event",
"id": "019a1f40-5f60-7b72-9d8e-9f0a1b2c3d4e",
"contact_id": "019a1e00-1a2b-7c3d-8e4f-5a6b7c8d9e0f"
}
#Listar eventos
GET/events
Do mais recente para o mais antigo, dos últimos 90 dias.
Parâmetros de consulta
limitintegerDe 1 a 100. Padrão: 20.afterstringCursor da próxima página.beforestringCursor da página anterior.namestringUm ou mais nomes separados por vírgula, comotrial.started,order.paid.contactstringID ou e-mail do contato.
Erros
- 422
validation_errorAlgum campo é inválido. A mensagem diz qual.
cURL
curl 'https://api.postal.so/events?name=trial.started&contact=ana%40exemplo.com.br' \
-H "Authorization: Bearer $POSTAL_API_KEY"
Node.js
const response = await fetch('https://api.postal.so/events?name=trial.started&contact=ana%40exemplo.com.br', {
headers: {
Authorization: `Bearer ${process.env.POSTAL_API_KEY}`,
},
});
const data = await response.json();
Python
import os
import requests
response = requests.get(
"https://api.postal.so/events",
headers={"Authorization": f"Bearer {os.environ['POSTAL_API_KEY']}"},
params={
"name": "trial.started",
"contact": "[email protected]",
},
timeout=10,
)
data = response.json()
PHP
$client = new \GuzzleHttp\Client(['base_uri' => 'https://api.postal.so']);
$response = $client->get('events', [
'headers' => ['Authorization' => 'Bearer '.getenv('POSTAL_API_KEY')],
'query' => [
'name' => 'trial.started',
'contact' => '[email protected]',
],
]);
$data = json_decode($response->getBody(), true);
{
"object": "list",
"has_more": false,
"data": [
{
"object": "event",
"id": "019a1f40-5f60-7b72-9d8e-9f0a1b2c3d4e",
"name": "trial.started",
"contact_id": "019a1e00-1a2b-7c3d-8e4f-5a6b7c8d9e0f",
"contact_email": "[email protected]",
"properties": {
"plan": "pro",
"source": "website",
"trial_days": 14
},
"occurred_at": "2026-10-24T14:20:00.000000Z",
"created_at": "2026-10-24T14:20:01.000000Z"
}
]
}
#Listar nomes de eventos
GET/events/names
Os nomes de evento que o time enviou, do visto mais recentemente para o mais antigo (até 100), com quantos de cada há guardados. Útil para montar gatilhos e filtros.
cURL
curl https://api.postal.so/events/names \
-H "Authorization: Bearer $POSTAL_API_KEY"
Node.js
const response = await fetch('https://api.postal.so/events/names', {
headers: {
Authorization: `Bearer ${process.env.POSTAL_API_KEY}`,
},
});
const data = await response.json();
Python
import os
import requests
response = requests.get(
"https://api.postal.so/events/names",
headers={"Authorization": f"Bearer {os.environ['POSTAL_API_KEY']}"},
timeout=10,
)
data = response.json()
PHP
$client = new \GuzzleHttp\Client(['base_uri' => 'https://api.postal.so']);
$response = $client->get('events/names', [
'headers' => ['Authorization' => 'Bearer '.getenv('POSTAL_API_KEY')],
]);
$data = json_decode($response->getBody(), true);
{
"object": "list",
"has_more": false,
"data": [
{
"name": "trial.started",
"count": 812,
"last_seen_at": "2026-10-24T14:20:01.000000Z"
},
{
"name": "order.paid",
"count": 4310,
"last_seen_at": "2026-10-24T14:02:47.000000Z"
}
]
}
#Obter um evento
GET/events/{id}
Parâmetros de caminho
idstringobrigatórioID do evento.
Erros
- 404
not_foundO recurso não existe ou é de outro time.
cURL
curl https://api.postal.so/events/019a1f40-5f60-7b72-9d8e-9f0a1b2c3d4e \
-H "Authorization: Bearer $POSTAL_API_KEY"
Node.js
const response = await fetch('https://api.postal.so/events/019a1f40-5f60-7b72-9d8e-9f0a1b2c3d4e', {
headers: {
Authorization: `Bearer ${process.env.POSTAL_API_KEY}`,
},
});
const data = await response.json();
Python
import os
import requests
response = requests.get(
"https://api.postal.so/events/019a1f40-5f60-7b72-9d8e-9f0a1b2c3d4e",
headers={"Authorization": f"Bearer {os.environ['POSTAL_API_KEY']}"},
timeout=10,
)
data = response.json()
PHP
$client = new \GuzzleHttp\Client(['base_uri' => 'https://api.postal.so']);
$response = $client->get('events/019a1f40-5f60-7b72-9d8e-9f0a1b2c3d4e', [
'headers' => ['Authorization' => 'Bearer '.getenv('POSTAL_API_KEY')],
]);
$data = json_decode($response->getBody(), true);
{
"object": "event",
"id": "019a1f40-5f60-7b72-9d8e-9f0a1b2c3d4e",
"name": "trial.started",
"contact_id": "019a1e00-1a2b-7c3d-8e4f-5a6b7c8d9e0f",
"contact_email": "[email protected]",
"properties": {
"plan": "pro",
"source": "website",
"trial_days": 14
},
"occurred_at": "2026-10-24T14:20:00.000000Z",
"created_at": "2026-10-24T14:20:01.000000Z"
}
#Boas práticas
- Envie do seu servidor, nunca do navegador: a chave de API não pode ficar exposta. Uma chave só de envio basta.
- Use uma
Idempotency-Keyderivada do fato (order-10482-paid), não da tentativa: repetir depois de um timeout não inicia a automação duas vezes. - Escolha nomes estáveis, no passado ou como fatos (
order.paid), e mande empropertiessó o que vai usar em filtros, condições e e-mails. Não envie senhas, números de cartão ou dados sensíveis. - Propriedades no primeiro nível (
plan) viram variáveis de template automaticamente ({{{plan}}}e{{{PLAN}}}); propriedades aninhadas são lidas com caminhos, comoitems.0.name.
// No seu backend, logo depois de liberar o teste grátis
await fetch('https://api.postal.so/events', {
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.POSTAL_API_KEY}`,
'Content-Type': 'application/json',
'Idempotency-Key': `trial-${account.id}`,
},
body: JSON.stringify({
name: 'trial.started',
contact: user.email,
properties: { plan: account.plan, source: account.source },
}),
});