Referência da API

Webhooks

Endpoints de webhook, eventos entregues, tentativas e reenvio.

Cadastre URLs para receber eventos em tempo real. Como verificar a assinatura, os formatos de cada evento e a política de novas tentativas estão no guia Webhooks.

#Listar webhooks

GET/webhooks

Chave de acesso total

Aceita limit, after e before. O segredo de assinatura não aparece na lista.

GET /webhooks

cURL

curl https://api.postal.so/webhooks \
  -H "Authorization: Bearer $POSTAL_API_KEY"

Node.js

const response = await fetch('https://api.postal.so/webhooks', {
  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/webhooks",
    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('webhooks', [
    'headers' => ['Authorization' => 'Bearer '.getenv('POSTAL_API_KEY')],
]);

$data = json_decode($response->getBody(), true);
200Resposta
{
  "object": "list",
  "has_more": false,
  "data": [
    {
      "object": "webhook",
      "id": "0199f6d2-3c4d-7e5f-8a6b-7c8d9e0f1a2b",
      "endpoint": "https://lojaexemplo.com.br/webhooks/postal",
      "events": ["email.delivered", "email.bounced", "email.complained"],
      "status": "enabled",
      "created_at": "2026-10-03T10:00:00.000000Z"
    }
  ]
}

#Criar um webhook

POST/webhooks

Chave de acesso total

Cadastra o endpoint e devolve o signing_secret (whsec_…) usado para assinar as entregas.

Corpo da requisição

  • endpointstringobrigatório
    URL HTTPS pública, até 2048 caracteres. Endereços de rede privada são recusados na entrega.
  • eventsstring[]obrigatório
    Um ou mais tipos de evento: email.sent, email.delivered, email.delivery_delayed, email.bounced, email.complained, email.opened, email.clicked, email.failed, email.scheduled, email.suppressed, email.received, domain.created, domain.updated, domain.deleted, suppression.added, suppression.removed, contact.created, contact.updated, contact.deleted, form.submitted, broadcast.sent.

Erros

  • 422validation_error
    URL que não é HTTPS, lista vazia ou evento desconhecido.
POST /webhooks

cURL

curl -X POST https://api.postal.so/webhooks \
  -H "Authorization: Bearer $POSTAL_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "endpoint": "https://lojaexemplo.com.br/webhooks/postal",
    "events": ["email.delivered", "email.bounced", "email.complained"]
  }'

Node.js

const response = await fetch('https://api.postal.so/webhooks', {
  method: 'POST',
  headers: {
    Authorization: `Bearer ${process.env.POSTAL_API_KEY}`,
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
    endpoint: 'https://lojaexemplo.com.br/webhooks/postal',
    events: ['email.delivered', 'email.bounced', 'email.complained'],
  }),
});

const data = await response.json();

Python

import os
import requests

response = requests.post(
    "https://api.postal.so/webhooks",
    headers={"Authorization": f"Bearer {os.environ['POSTAL_API_KEY']}"},
    json={
        "endpoint": "https://lojaexemplo.com.br/webhooks/postal",
        "events": ["email.delivered", "email.bounced", "email.complained"],
    },
    timeout=10,
)

data = response.json()

PHP

$client = new \GuzzleHttp\Client(['base_uri' => 'https://api.postal.so']);

$response = $client->post('webhooks', [
    'headers' => ['Authorization' => 'Bearer '.getenv('POSTAL_API_KEY')],
    'json' => [
        'endpoint' => 'https://lojaexemplo.com.br/webhooks/postal',
        'events' => ['email.delivered', 'email.bounced', 'email.complained'],
    ],
]);

$data = json_decode($response->getBody(), true);
201Resposta
{
  "object": "webhook",
  "id": "0199f6d2-3c4d-7e5f-8a6b-7c8d9e0f1a2b",
  "signing_secret": "whsec_c2VncmVkby1kZS1leGVtcGxvLTEyMzQ1Ng=="
}

#Obter um webhook

GET/webhooks/{id}

Chave de acesso total

Devolve o webhook com o signing_secret atual.

Parâmetros de caminho

  • idstringobrigatório
    ID do webhook.

Erros

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

cURL

curl https://api.postal.so/webhooks/0199f6d2-3c4d-7e5f-8a6b-7c8d9e0f1a2b \
  -H "Authorization: Bearer $POSTAL_API_KEY"

Node.js

const response = await fetch('https://api.postal.so/webhooks/0199f6d2-3c4d-7e5f-8a6b-7c8d9e0f1a2b', {
  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/webhooks/0199f6d2-3c4d-7e5f-8a6b-7c8d9e0f1a2b",
    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('webhooks/0199f6d2-3c4d-7e5f-8a6b-7c8d9e0f1a2b', [
    'headers' => ['Authorization' => 'Bearer '.getenv('POSTAL_API_KEY')],
]);

$data = json_decode($response->getBody(), true);
200Resposta
{
  "object": "webhook",
  "id": "0199f6d2-3c4d-7e5f-8a6b-7c8d9e0f1a2b",
  "endpoint": "https://lojaexemplo.com.br/webhooks/postal",
  "events": ["email.delivered", "email.bounced", "email.complained"],
  "status": "enabled",
  "created_at": "2026-10-03T10:00:00.000000Z",
  "signing_secret": "whsec_c2VncmVkby1kZS1leGVtcGxvLTEyMzQ1Ng=="
}

#Atualizar um webhook

PATCH/webhooks/{id}

Chave de acesso total

Muda a URL, os eventos ou pausa o webhook. Com disabled, nenhum evento é entregue (nem as novas tentativas pendentes).

Parâmetros de caminho

  • idstringobrigatório
    ID do webhook.

Corpo da requisição

  • endpointstring
    Nova URL HTTPS.
  • eventsstring[]
    Nova lista de eventos.
  • statusstring
    enabled ou disabled.

Erros

  • 422validation_error
    Algum campo é inválido. A mensagem diz qual.
  • 404not_found
    O recurso não existe ou é de outro time.
PATCH /webhooks/{id}

cURL

curl -X PATCH https://api.postal.so/webhooks/0199f6d2-3c4d-7e5f-8a6b-7c8d9e0f1a2b \
  -H "Authorization: Bearer $POSTAL_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "status": "disabled"
  }'

Node.js

const response = await fetch('https://api.postal.so/webhooks/0199f6d2-3c4d-7e5f-8a6b-7c8d9e0f1a2b', {
  method: 'PATCH',
  headers: {
    Authorization: `Bearer ${process.env.POSTAL_API_KEY}`,
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
    status: 'disabled',
  }),
});

const data = await response.json();

Python

import os
import requests

response = requests.patch(
    "https://api.postal.so/webhooks/0199f6d2-3c4d-7e5f-8a6b-7c8d9e0f1a2b",
    headers={"Authorization": f"Bearer {os.environ['POSTAL_API_KEY']}"},
    json={
        "status": "disabled",
    },
    timeout=10,
)

data = response.json()

PHP

$client = new \GuzzleHttp\Client(['base_uri' => 'https://api.postal.so']);

$response = $client->patch('webhooks/0199f6d2-3c4d-7e5f-8a6b-7c8d9e0f1a2b', [
    'headers' => ['Authorization' => 'Bearer '.getenv('POSTAL_API_KEY')],
    'json' => [
        'status' => 'disabled',
    ],
]);

$data = json_decode($response->getBody(), true);
200Resposta
{
  "object": "webhook",
  "id": "0199f6d2-3c4d-7e5f-8a6b-7c8d9e0f1a2b"
}

#Excluir um webhook

DELETE/webhooks/{id}

Chave de acesso total

Parâmetros de caminho

  • idstringobrigatório
    ID do webhook.

Erros

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

cURL

curl -X DELETE https://api.postal.so/webhooks/0199f6d2-3c4d-7e5f-8a6b-7c8d9e0f1a2b \
  -H "Authorization: Bearer $POSTAL_API_KEY"

Node.js

const response = await fetch('https://api.postal.so/webhooks/0199f6d2-3c4d-7e5f-8a6b-7c8d9e0f1a2b', {
  method: 'DELETE',
  headers: {
    Authorization: `Bearer ${process.env.POSTAL_API_KEY}`,
  },
});

const data = await response.json();

Python

import os
import requests

response = requests.delete(
    "https://api.postal.so/webhooks/0199f6d2-3c4d-7e5f-8a6b-7c8d9e0f1a2b",
    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->delete('webhooks/0199f6d2-3c4d-7e5f-8a6b-7c8d9e0f1a2b', [
    'headers' => ['Authorization' => 'Bearer '.getenv('POSTAL_API_KEY')],
]);

$data = json_decode($response->getBody(), true);
200Resposta
{
  "object": "webhook",
  "id": "0199f6d2-3c4d-7e5f-8a6b-7c8d9e0f1a2b",
  "deleted": true
}

#Trocar o segredo de assinatura

POST/webhooks/{id}/signing-secret/rotate

Chave de acesso total

Gera um novo signing_secret, que passa a valer imediatamente. Atualize o seu servidor logo em seguida: entregas assinadas com o segredo novo falham na verificação com o antigo.

Parâmetros de caminho

  • idstringobrigatório
    ID do webhook.

Erros

  • 404not_found
    O recurso não existe ou é de outro time.
POST /webhooks/{id}/signing-secret/rotate

cURL

curl -X POST https://api.postal.so/webhooks/0199f6d2-3c4d-7e5f-8a6b-7c8d9e0f1a2b/signing-secret/rotate \
  -H "Authorization: Bearer $POSTAL_API_KEY"

Node.js

const response = await fetch('https://api.postal.so/webhooks/0199f6d2-3c4d-7e5f-8a6b-7c8d9e0f1a2b/signing-secret/rotate', {
  method: 'POST',
  headers: {
    Authorization: `Bearer ${process.env.POSTAL_API_KEY}`,
  },
});

const data = await response.json();

Python

import os
import requests

response = requests.post(
    "https://api.postal.so/webhooks/0199f6d2-3c4d-7e5f-8a6b-7c8d9e0f1a2b/signing-secret/rotate",
    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->post('webhooks/0199f6d2-3c4d-7e5f-8a6b-7c8d9e0f1a2b/signing-secret/rotate', [
    'headers' => ['Authorization' => 'Bearer '.getenv('POSTAL_API_KEY')],
]);

$data = json_decode($response->getBody(), true);
200Resposta
{
  "object": "webhook",
  "id": "0199f6d2-3c4d-7e5f-8a6b-7c8d9e0f1a2b",
  "signing_secret": "whsec_bm92by1zZWdyZWRvLWRlLWV4ZW1wbG8tOTg3Ng=="
}

#Listar eventos de um webhook

GET/webhooks/{id}/events

Chave de acesso total

Os eventos enviados para este webhook, com o status da entrega: pending, attempting (falhou e tem nova tentativa em next_attempt_at), success ou failed (esgotou as tentativas). Aceita limit, after e before.

Parâmetros de caminho

  • idstringobrigatório
    ID do webhook.

Erros

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

cURL

curl https://api.postal.so/webhooks/0199f6d2-3c4d-7e5f-8a6b-7c8d9e0f1a2b/events \
  -H "Authorization: Bearer $POSTAL_API_KEY"

Node.js

const response = await fetch('https://api.postal.so/webhooks/0199f6d2-3c4d-7e5f-8a6b-7c8d9e0f1a2b/events', {
  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/webhooks/0199f6d2-3c4d-7e5f-8a6b-7c8d9e0f1a2b/events",
    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('webhooks/0199f6d2-3c4d-7e5f-8a6b-7c8d9e0f1a2b/events', [
    'headers' => ['Authorization' => 'Bearer '.getenv('POSTAL_API_KEY')],
]);

$data = json_decode($response->getBody(), true);
200Resposta
{
  "object": "list",
  "has_more": true,
  "data": [
    {
      "object": "webhook_event",
      "id": "019a1b2c-5e6f-7a80-9b1c-2d3e4f5a6b7c",
      "type": "email.delivered",
      "created_at": "2026-10-24T13:02:14.000000Z",
      "status": "success",
      "attempts": 1,
      "next_attempt_at": null
    }
  ]
}

#Obter um evento

GET/webhooks/{id}/events/{event_id}

Chave de acesso total

Devolve o evento com o payload exato que foi (ou será) enviado.

Parâmetros de caminho

  • idstringobrigatório
    ID do webhook.
  • event_idstringobrigatório
    ID do evento.

Erros

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

cURL

curl https://api.postal.so/webhooks/0199f6d2-3c4d-7e5f-8a6b-7c8d9e0f1a2b/events/019a1b2c-5e6f-7a80-9b1c-2d3e4f5a6b7c \
  -H "Authorization: Bearer $POSTAL_API_KEY"

Node.js

const response = await fetch('https://api.postal.so/webhooks/0199f6d2-3c4d-7e5f-8a6b-7c8d9e0f1a2b/events/019a1b2c-5e6f-7a80-9b1c-2d3e4f5a6b7c', {
  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/webhooks/0199f6d2-3c4d-7e5f-8a6b-7c8d9e0f1a2b/events/019a1b2c-5e6f-7a80-9b1c-2d3e4f5a6b7c",
    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('webhooks/0199f6d2-3c4d-7e5f-8a6b-7c8d9e0f1a2b/events/019a1b2c-5e6f-7a80-9b1c-2d3e4f5a6b7c', [
    'headers' => ['Authorization' => 'Bearer '.getenv('POSTAL_API_KEY')],
]);

$data = json_decode($response->getBody(), true);
200Resposta
{
  "object": "webhook_event",
  "id": "019a1b2c-5e6f-7a80-9b1c-2d3e4f5a6b7c",
  "type": "email.delivered",
  "created_at": "2026-10-24T13:02:14.000000Z",
  "status": "success",
  "attempts": 1,
  "next_attempt_at": null,
  "payload": {
    "type": "email.delivered",
    "created_at": "2026-10-24T13:02:14.120Z",
    "data": {
      "email_id": "019a1b2c-4d5e-7f60-8a9b-0c1d2e3f4a5b",
      "created_at": "2026-10-24T13:02:11.000Z",
      "from": "Loja Exemplo <[email protected]>",
      "to": ["[email protected]"],
      "subject": "Seu pedido #10482 saiu para entrega",
      "tags": {
        "category": "shipping"
      }
    }
  }
}

#Listar tentativas de entrega

GET/webhooks/{id}/events/{event_id}/attempts

Chave de acesso total

Cada tentativa de entrega com o status HTTP, o início da resposta do seu servidor (até 2.000 caracteres), o erro de conexão, se houve, e a duração.

Parâmetros de caminho

  • idstringobrigatório
    ID do webhook.
  • event_idstringobrigatório
    ID do evento.

Erros

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

cURL

curl https://api.postal.so/webhooks/0199f6d2-3c4d-7e5f-8a6b-7c8d9e0f1a2b/events/019a1b2c-5e6f-7a80-9b1c-2d3e4f5a6b7c/attempts \
  -H "Authorization: Bearer $POSTAL_API_KEY"

Node.js

const response = await fetch('https://api.postal.so/webhooks/0199f6d2-3c4d-7e5f-8a6b-7c8d9e0f1a2b/events/019a1b2c-5e6f-7a80-9b1c-2d3e4f5a6b7c/attempts', {
  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/webhooks/0199f6d2-3c4d-7e5f-8a6b-7c8d9e0f1a2b/events/019a1b2c-5e6f-7a80-9b1c-2d3e4f5a6b7c/attempts",
    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('webhooks/0199f6d2-3c4d-7e5f-8a6b-7c8d9e0f1a2b/events/019a1b2c-5e6f-7a80-9b1c-2d3e4f5a6b7c/attempts', [
    'headers' => ['Authorization' => 'Bearer '.getenv('POSTAL_API_KEY')],
]);

$data = json_decode($response->getBody(), true);
200Resposta
{
  "object": "list",
  "has_more": false,
  "data": [
    {
      "id": "019a1b2c-5e70-7b91-8c2d-3e4f5a6b7c8d",
      "http_status_code": 200,
      "response": "{\"ok\":true}",
      "error": null,
      "duration_ms": 184,
      "sent_at": "2026-10-24T13:02:14.000000Z"
    }
  ]
}

#Reenviar um evento

POST/webhooks/{id}/events/{event_id}/replay

Chave de acesso total

Entrega o evento de novo na hora, mesmo que já tenha dado certo ou falhado de vez. O contador de tentativas recomeça. A entrega usa o mesmo ID de mensagem (webhook-id), então o seu servidor pode reconhecer a repetição.

Parâmetros de caminho

  • idstringobrigatório
    ID do webhook.
  • event_idstringobrigatório
    ID do evento.

Erros

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

cURL

curl -X POST https://api.postal.so/webhooks/0199f6d2-3c4d-7e5f-8a6b-7c8d9e0f1a2b/events/019a1b2c-5e6f-7a80-9b1c-2d3e4f5a6b7c/replay \
  -H "Authorization: Bearer $POSTAL_API_KEY"

Node.js

const response = await fetch('https://api.postal.so/webhooks/0199f6d2-3c4d-7e5f-8a6b-7c8d9e0f1a2b/events/019a1b2c-5e6f-7a80-9b1c-2d3e4f5a6b7c/replay', {
  method: 'POST',
  headers: {
    Authorization: `Bearer ${process.env.POSTAL_API_KEY}`,
  },
});

const data = await response.json();

Python

import os
import requests

response = requests.post(
    "https://api.postal.so/webhooks/0199f6d2-3c4d-7e5f-8a6b-7c8d9e0f1a2b/events/019a1b2c-5e6f-7a80-9b1c-2d3e4f5a6b7c/replay",
    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->post('webhooks/0199f6d2-3c4d-7e5f-8a6b-7c8d9e0f1a2b/events/019a1b2c-5e6f-7a80-9b1c-2d3e4f5a6b7c/replay', [
    'headers' => ['Authorization' => 'Bearer '.getenv('POSTAL_API_KEY')],
]);

$data = json_decode($response->getBody(), true);
200Resposta
{
  "object": "webhook_event",
  "id": "019a1b2c-5e6f-7a80-9b1c-2d3e4f5a6b7c"
}