Marketing

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

Acesso total ou só envio

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-Keystring
    Identificador único do evento no seu sistema, até 256 caracteres, como trial-8812. Recomendado.

Corpo da requisição

  • namestringobrigatório
    Nome do evento, até 100 caracteres: letras, números, ., _, : e -, começando com letra ou número. Exemplos: trial.started, order_paid, checkout:abandoned.
  • contactstring
    ID ou e-mail do contato. Um e-mail desconhecido cria o contato; um ID desconhecido gera 404.
  • propertiesobject
    Dados 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_atstring
    Quando aconteceu, em ISO 8601. Padrão: agora. Não pode passar de 24 horas no futuro.

Erros

  • 422validation_error
    Nome ausente ou inválido, properties que não é objeto ou passa de 16 KB, contact que não é ID nem e-mail, ou occurred_at no futuro.
  • 404not_found
    Nenhum contato com o ID de contact.
  • 403plan_limit_reached
    O contato seria criado, mas o plano não permite mais contatos.
  • 409invalid_idempotent_request
    A mesma Idempotency-Key foi usada com outro corpo.
POST /events

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);
201Resposta
{
  "object": "event",
  "id": "019a1f40-5f60-7b72-9d8e-9f0a1b2c3d4e",
  "contact_id": "019a1e00-1a2b-7c3d-8e4f-5a6b7c8d9e0f"
}

#Listar eventos

GET/events

Chave de acesso total

Do mais recente para o mais antigo, dos últimos 90 dias.

Parâmetros de consulta

  • limitinteger
    De 1 a 100. Padrão: 20.
  • afterstring
    Cursor da próxima página.
  • beforestring
    Cursor da página anterior.
  • namestring
    Um ou mais nomes separados por vírgula, como trial.started,order.paid.
  • contactstring
    ID ou e-mail do contato.

Erros

  • 422validation_error
    Algum campo é inválido. A mensagem diz qual.
GET /events

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);
200Resposta
{
  "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

Chave de acesso total

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.

GET /events/names

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);
200Resposta
{
  "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}

Chave de acesso total

Parâmetros de caminho

  • idstringobrigatório
    ID do evento.

Erros

  • 404not_found
    O recurso não existe ou é de outro time.
GET /events/{id}

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);
200Resposta
{
  "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-Key derivada 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 em properties só 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, como items.0.name.
Node.js
// 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 },
  }),
});