Iniciar sesión Crear cuenta
Marketing

Eventos

Envía lo que ocurre en tu producto para iniciar automatizaciones y personalizar correos electrónicos.

Los eventos le cuentan a Postal lo que ocurre en tu producto: una prueba gratis que empezó, un pedido pagado, un carrito abandonado. Cada evento puede estar vinculado a un contacto y activa las automatizaciones con el disparador de ese nombre. Para enviar eventos, basta una clave solo de envío, la misma de tu backend; para consultar, una de acceso total. Los eventos se guardan durante 90 días.

El evento también puede crear o actualizar el contacto (por ID, correo electrónico o external_id, con contact_properties) antes de iniciar las automatizaciones, así que un solo POST /events basta para registrar a la persona y reaccionar a lo que hizo. Los SDKs de Node.js y PHP (events.send) y la CLI (postal events send) envían eventos.

#Enviar un evento

POST/events

Acceso total o solo envío

Registra el evento y, si tiene contacto, inicia las automatizaciones activas cuyo disparador es este evento (y cuyos filtros coinciden con las propiedades). En contact, un correo electrónico que aún no está en la audiencia crea el contacto, lo que dispara el webhook contact.created y las automatizaciones de «contacto creado». Sin contact, el evento queda registrado, pero no inicia automatizaciones.

Una dirección de la lista de supresión nunca se convierte en contacto: la respuesta es 422 email_suppressed y el evento no se registra. Con contact_properties, el contacto se actualiza antes de que empiecen las automatizaciones, así que tanto las de contact.property_changed como las de evento ven los valores nuevos.

Acepta Idempotency-Key: repetir la solicitud con la misma clave dentro de 24 horas devuelve la misma respuesta, sin registrar el evento de nuevo ni iniciar otra ejecución.

Cabeceras

  • Idempotency-Keystring
    Identificador único del evento en tu sistema, hasta 256 caracteres, como trial-8812. Recomendado.
  • X-Postal-Sourcestring
    Etiqueta del origen del cambio en el contacto (consulta source más abajo).

Cuerpo de la solicitud

  • namestringobligatorio
    Nombre del evento, hasta 100 caracteres: letras, números, ., _, : y -, empezando por letra o número. Ejemplos: trial.started, order_paid, checkout:abandoned.
  • contactstring | object
    ID o correo electrónico del contacto. Un correo desconocido crea el contacto; un ID desconocido genera 404. También acepta external_id:<id> (o user_id:<id>), para encontrar el contacto por su ID en tu sistema, o un objeto.
    • idstring
      ID del contacto.
    • emailstring
      Correo electrónico del contacto. Crea el contacto si no existe.
    • external_idstring
      ID de la persona en tu sistema, hasta 255 caracteres. Alias: user_id.
    • first_namestring
      Nome.
    • last_namestring
      Sobrenome.
  • external_idstring
    Atajo de contact.external_id, hasta 255 caracteres (alias: user_id). El contacto se busca por ID, luego por external_id y luego por correo electrónico; un external_id sin contacto correspondiente y sin correo electrónico genera 422.
  • contact_propertiesobject
    Propiedades del contacto que se van a actualizar, por clave, como {"plan": "pro"}. La propiedad debe existir en propiedades de contacto y el valor sigue su tipo; un valor vacío elimina la propiedad.
  • sourcestring
    Etiqueta de dónde vino el cambio en el contacto, hasta 64 caracteres (letras minúsculas, números, ., _, : y -), como meu-app. Va en el campo source de los webhooks contact.*. También puede ir en el encabezado X-Postal-Source; si llegan los dos, vale el del cuerpo. Las etiquetas propias de Postal (como dashboard, form, import y automation) y las que empiezan por crm: están reservadas.
  • propertiesobject
    Datos del evento, en JSON, hasta 16 KB. Quedan disponibles en los filtros y condiciones de las automatizaciones y como variables en los correos electrónicos ({{event.plan}}, {{event.items.0.name}}).
  • occurred_atstring
    Cuándo ocurrió, en ISO 8601. Valor predeterminado: ahora. No puede superar las 24 horas en el futuro.

Errores

  • 422validation_error
    Nombre ausente o no válido, properties que no es un objeto o supera los 16 KB, contact que no es un ID ni un correo electrónico, occurred_at en el futuro, source no válido o reservado, contact_properties con una propiedad que no existe o un valor del tipo equivocado, o un external_id sin contacto correspondiente y sin correo electrónico.
  • 422email_suppressed
    El correo electrónico está en la lista de supresión y no puede convertirse en contacto. Quítalo de ahí primero.
  • 404not_found
    Ningún contacto con el ID de contact.
  • 403plan_limit_reached
    Se crearía el contacto, pero el plan ya no permite más contactos.
  • 409invalid_idempotent_request
    La misma Idempotency-Key se usó con otro cuerpo.
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);
201Respuesta
{
  "object": "event",
  "id": "019a1f40-5f60-7b72-9d8e-9f0a1b2c3d4e",
  "contact_id": "019a1e00-1a2b-7c3d-8e4f-5a6b7c8d9e0f"
}
Otro cuerpo: contacto por el ID en tu sistema, ya con propiedades
{
  "name": "order.paid",
  "contact": {
    "external_id": "user_42",
    "email": "[email protected]"
  },
  "properties": {
    "order_id": "10482",
    "total": 199.9
  },
  "contact_properties": {
    "plan": "pro"
  },
  "source": "meu-app"
}

#Listar eventos

GET/events

Clave de acceso total

Del más reciente al más antiguo, de los últimos 90 días.

Parámetros de consulta

  • limitinteger
    De 1 a 100. Predeterminado: 20.
  • afterstring
    Cursor de la página siguiente.
  • beforestring
    Cursor de la página anterior.
  • namestring
    Uno o más nombres separados por comas, como trial.started,order.paid.
  • contactstring
    ID o correo electrónico del contacto.

Errores

  • 422validation_error
    Algún campo no es válido. El mensaje indica cuál.
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);
200Respuesta
{
  "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 nombres de eventos

GET/events/names

Clave de acceso total

Los nombres de evento que envió el equipo, del visto más recientemente al más antiguo (hasta 100), con cuántos hay guardados de cada uno. Útil para armar disparadores y 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);
200Respuesta
{
  "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"
    }
  ]
}

#Obtener un evento

GET/events/{id}

Clave de acceso total

Parámetros de ruta

  • idstringobligatorio
    ID del evento.

Errores

  • 404not_found
    El recurso no existe o es de otro equipo.
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);
200Respuesta
{
  "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"
}

#Buenas prácticas

  • Envía desde tu servidor, nunca desde el navegador: la clave de API no puede quedar expuesta. Basta una clave solo de envío.
  • Usa una Idempotency-Key derivada del hecho (order-10482-paid), no del intento: repetir después de un timeout no inicia la automatización dos veces.
  • Elige nombres estables, en pasado o como hechos (order.paid), y manda en properties solo lo que vayas a usar en filtros, condiciones y correos electrónicos. No envíes contraseñas, números de tarjeta ni datos sensibles.
  • Las propiedades de primer nivel (plan) se convierten automáticamente en variables de plantilla ({{{plan}}} y {{{PLAN}}}); las propiedades anidadas se leen con rutas, como items.0.name.
Desde tu backend

cURL

# No seu backend, logo depois de liberar o teste grátis
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"}}'

Node.js

import { Postal } from 'postal-so'

const postal = new Postal() // lê POSTAL_API_KEY

// No seu backend, logo depois de liberar o teste grátis
await postal.events.send(
  {
    name: 'trial.started',
    contact: user.email,
    properties: { plan: account.plan, source: account.source },
  },
  { idempotencyKey: `trial-${account.id}` },
)

Python

import os
import requests

# No seu backend, logo depois de liberar o teste grátis
requests.post(
    "https://api.postal.so/events",
    headers={
        "Authorization": f"Bearer {os.environ['POSTAL_API_KEY']}",
        "Idempotency-Key": f"trial-{account.id}",
    },
    json={
        "name": "trial.started",
        "contact": user.email,
        "properties": {"plan": account.plan, "source": account.source},
    },
    timeout=10,
)

PHP

use Postal\Client;

$postal = new Client(); // lê POSTAL_API_KEY

// No seu backend, logo depois de liberar o teste grátis
$postal->events->send([
    'name' => 'trial.started',
    'contact' => $user->email,
    'properties' => ['plan' => $account->plan, 'source' => $account->source],
], 'trial-'.$account->id);