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
Aceita limit, after e before. O segredo de assinatura não aparece na lista.
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);
{
"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
Cadastra o endpoint e devolve o signing_secret (whsec_…) usado para assinar as entregas.
Corpo da requisição
endpointstringobrigatórioURL HTTPS pública, até 2048 caracteres. Endereços de rede privada são recusados na entrega.eventsstring[]obrigatórioUm 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
- 422
validation_errorURL que não é HTTPS, lista vazia ou evento desconhecido.
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);
{
"object": "webhook",
"id": "0199f6d2-3c4d-7e5f-8a6b-7c8d9e0f1a2b",
"signing_secret": "whsec_c2VncmVkby1kZS1leGVtcGxvLTEyMzQ1Ng=="
}
#Obter um webhook
GET/webhooks/{id}
Devolve o webhook com o signing_secret atual.
Parâmetros de caminho
idstringobrigatórioID do webhook.
Erros
- 404
not_foundO recurso não existe ou é de outro time.
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);
{
"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}
Muda a URL, os eventos ou pausa o webhook. Com disabled, nenhum evento é entregue (nem as novas tentativas pendentes).
Parâmetros de caminho
idstringobrigatórioID do webhook.
Corpo da requisição
endpointstringNova URL HTTPS.eventsstring[]Nova lista de eventos.statusstringenabledoudisabled.
Erros
- 422
validation_errorAlgum campo é inválido. A mensagem diz qual. - 404
not_foundO recurso não existe ou é de outro time.
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);
{
"object": "webhook",
"id": "0199f6d2-3c4d-7e5f-8a6b-7c8d9e0f1a2b"
}
#Excluir um webhook
DELETE/webhooks/{id}
Parâmetros de caminho
idstringobrigatórioID do webhook.
Erros
- 404
not_foundO recurso não existe ou é de outro time.
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);
{
"object": "webhook",
"id": "0199f6d2-3c4d-7e5f-8a6b-7c8d9e0f1a2b",
"deleted": true
}
#Trocar o segredo de assinatura
POST/webhooks/{id}/signing-secret/rotate
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órioID do webhook.
Erros
- 404
not_foundO recurso não existe ou é de outro time.
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);
{
"object": "webhook",
"id": "0199f6d2-3c4d-7e5f-8a6b-7c8d9e0f1a2b",
"signing_secret": "whsec_bm92by1zZWdyZWRvLWRlLWV4ZW1wbG8tOTg3Ng=="
}
#Listar eventos de um webhook
GET/webhooks/{id}/events
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órioID do webhook.
Erros
- 404
not_foundO recurso não existe ou é de outro time.
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);
{
"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}
Devolve o evento com o payload exato que foi (ou será) enviado.
Parâmetros de caminho
idstringobrigatórioID do webhook.event_idstringobrigatórioID do evento.
Erros
- 404
not_foundO recurso não existe ou é de outro time.
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);
{
"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
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órioID do webhook.event_idstringobrigatórioID do evento.
Erros
- 404
not_foundO recurso não existe ou é de outro time.
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);
{
"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
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órioID do webhook.event_idstringobrigatórioID do evento.
Erros
- 404
not_foundO recurso não existe ou é de outro time.
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);
{
"object": "webhook_event",
"id": "019a1b2c-5e6f-7a80-9b1c-2d3e4f5a6b7c"
}