Avisos automáticos
Quer ser avisado quando seu post foi publicado, falhou ou precisou reconectar a rede? A gente avisa seu sistema em tempo real.
Esta é uma página técnica. Se você só usa o painel do Outposted, pode pular — o painel já te mostra status em tempo real.
Pra quê isso me serve
Se você é dev (ou tem dev), webhook é como o Outposted avisa seu sistema quando algo acontece. Em vez de você ficar perguntando a cada 30s "já foi?", a gente bate na sua porta quando tem novidade.
Exemplos:
- Agência: quando o post foi publicado, salvar a URL pública na ficha do cliente.
- Dashboard interno: atualizar "1.247 posts publicados este mês" em tempo real.
- Slack interno: quando um post falhou, mandar mensagem no canal de operações.
- Aprovação editorial: quando a rede aceitou, marcar a peça como "no ar".
Os 4 eventos que a gente envia
| Evento | Quando dispara |
|---|---|
content.published | Todas as redes do post publicaram com sucesso. |
content.partial | Algumas publicaram, outras falharam. |
content.failed | Nenhuma rede publicou. |
connection.expired | Uma rede precisa ser reconectada (você trocou senha, etc). |
Como funciona
- Você cria um endpoint no seu sistema — um endereço público que vai receber os avisos.
- Você registra esse endpoint no Outposted, dizendo quais eventos quer receber.
- Toda vez que algo acontece, a gente faz um POST pro seu endpoint com os detalhes.
- Seu sistema recebe e processa.
Como webhook bate em endereço público, qualquer pessoa que descobrir a URL pode tentar mandar um POST falso. Pra você ter certeza que o aviso é nosso, a gente assina cada POST com uma assinatura digital. A seção pra integradores explica como verificar.
Você tem agência com 12 clientes. Cada cliente publica 30 posts/mês. Você quer atualizar dashboard interno, receber alerta no Slack quando algum post falhar, marcar peça como publicada no Notion.
Você registra um endpoint só, assina os 4 eventos. Seu backend recebe o POST, decide o que fazer. Daí pra frente, não precisa mais ficar olhando o painel. Tudo chega na sua mão.
Se seu endpoint estiver fora do ar
Acontece. Servidor reinicia, deploy, instabilidade. A gente tenta de novo várias vezes antes de desistir:
| Tentativa | Quanto tempo depois |
|---|---|
| 1 | imediato |
| 2 | +1 minuto |
| 3 | +5 minutos |
| 4 | +30 minutos |
| 5 | +2 horas |
| 6 | +12 horas (última tentativa) |
Depois de 6 tentativas, marcamos o aviso como "morto" (DLQ — dead letter queue). Você ainda pode forçar reenvio manual quando consertar (veja seção pra integradores).
A gente considera "deu certo" quando seu endpoint responde 2xx em até 30s. Status 5xx, timeout ou conexão recusada = tenta de novo. Status 4xx (exceto 408/429) = sua decisão consciente, paramos.
Setup técnico
Pra integradores.
Criar um endpoint
curl -X POST "https://api.outposted.one/api/v1/workspaces/$WS_ID/webhooks" \
-H "Authorization: Bearer opst_live_..." \
-H "Content-Type: application/json" \
-d '{
"url": "https://api.minhaempresa.com/outposted/webhook",
"description": "prod backend",
"enabled_events": [
"content.published",
"content.partial",
"content.failed"
]
}'{
"id": "wh_abc...",
"url": "https://api.minhaempresa.com/outposted/webhook",
"secret": "whsec_def456...",
"active": true,
"enabled_events": ["content.published", "content.partial", "content.failed"]
}O secret aparece uma vez só. Guarde em variável de ambiente (OUTPOSTED_WEBHOOK_SECRET).
Payload + headers de assinatura
POST /outposted/webhook
Content-Type: application/json
Outposted-Timestamp: 1735350120
Outposted-Signature: sha256=a3f...
Outposted-Webhook-Id: wh_abc...
Outposted-Delivery-Id: del_xyz...
{
"id": "evt_...",
"type": "content.published",
"created_at": "2026-06-05T02:12:14Z",
"data": {
"content_id": "cef7e46c-...",
"workspace_id": "e9fe5698-...",
"jobs": [
{
"platform_slug": "threads",
"status": "published",
"external_post_url": "https://www.threads.com/..."
}
]
}
}Verificar a assinatura (Node.js)
HMAC-SHA256 do payload {timestamp}.{body} usando o secret como chave. Constant-time compare contra Outposted-Signature.
import { createHmac, timingSafeEqual } from 'node:crypto';
function verifyWebhook(
payload: string,
signatureHeader: string,
timestampHeader: string,
secret: string,
toleranceSeconds = 300,
): boolean {
const timestamp = parseInt(timestampHeader, 10);
const now = Math.floor(Date.now() / 1000);
if (Math.abs(now - timestamp) > toleranceSeconds) return false;
const presented = signatureHeader.replace(/^sha256=/, '');
const computed = createHmac('sha256', secret)
.update(`${timestamp}.${payload}`)
.digest('hex');
const a = Buffer.from(presented, 'hex');
const b = Buffer.from(computed, 'hex');
if (a.length !== b.length) return false;
return timingSafeEqual(a, b);
}
// Express
app.post('/outposted/webhook', express.raw({ type: 'application/json' }), (req, res) => {
const valid = verifyWebhook(
req.body.toString(),
req.header('outposted-signature')!,
req.header('outposted-timestamp')!,
process.env.OUTPOSTED_WEBHOOK_SECRET!,
);
if (!valid) return res.status(401).send('invalid signature');
res.status(200).send('ok');
});Via SDK
import { Outposted } from '@outposted/node';
const valid = Outposted.webhooks.verify({
payload: rawBody,
signature: req.header('outposted-signature')!,
timestamp: req.header('outposted-timestamp')!,
secret: process.env.OUTPOSTED_WEBHOOK_SECRET!,
});Teste seu payload/secret na página do playground. Roda 100% no seu browser, o secret nunca sai dele.
Inspecionar deliveries
curl "https://api.outposted.one/api/v1/workspaces/$WS_ID/webhooks/$WH_ID/deliveries?status=failed&limit=25" \
-H "Authorization: Bearer opst_live_..."Redelivery manual
curl -X POST "https://api.outposted.one/api/v1/workspaces/$WS_ID/deliveries/$DEL_ID/redeliver" \
-H "Authorization: Bearer opst_live_..."Endpoint público de debug
Sem auth, valida uma assinatura específica. Útil pra confirmar que sua implementação bate com a nossa antes de plugar em produção:
curl -X POST "https://api.outposted.one/api/v1/webhooks/verify" \
-H "Content-Type: application/json" \
-d '{
"secret": "whsec_...",
"signature_header": "sha256=a3f...",
"payload": "1735350120.{\"id\":\"evt_...\"}",
"tolerance_seconds": 300
}'{
"valid": true,
"checks": {
"signature_format": true,
"timestamp_within_tolerance": true,
"hmac_matches": true
},
"reason": null
}