Webhook Relay
Envie webhooks assinados para os seus clientes, com retry automático, log de tentativas e replay, e redistribua webhooks de Stripe, GitHub e outros provedores.
BetaEm beta: a ferramenta está em fase de testes com contas selecionadas e pode mudar antes do lançamento. Se a sua conta ainda não tiver acesso, as chamadas respondem webhook.relay_disabled; fale com o suporte para entrar nos testes.
Cadastre os endpoints dos seus clientes e publique eventos com uma chamada: a entrega é assinada no padrão Standard Webhooks, tem retry automático com backoff por até ~27 horas, log de cada tentativa e replay. Também recebe webhooks de terceiros (Stripe, GitHub, Shopify…), verifica a assinatura e redistribui para os seus endpoints, inclusive em Slack, Discord e Teams. Organize tudo em projetos, um por loja ou cliente.
- Base path
/v1/tools/webhooks- Escopos
tools:webhooks:readtools:webhooks:sendtools:webhooks:managetools:readtools:manage
Visão geral
O Webhook Relay é a infraestrutura de webhooks da sua aplicação. Em vez de manter filas, retries e assinaturas, você publica um evento com uma chamada e a plataforma entrega em cada endpoint inscrito, com o mesmo motor que envia os webhooks da conta.
- Projeto: agrupa endpoints, fontes, rotas, tipos de evento e mensagens, para separar configurações (uma loja, um cliente, um ambiente). Toda conta tem um projeto padrão; veja Projetos.
- Endpoint: a URL que recebe os eventos (normalmente do seu cliente, ou um canal do Slack, Discord ou Teams), com os eventos que assina e um
secretpróprio. - Mensagem: um evento que você publica (
event_type+payload). - Entrega: uma mensagem a caminho de um endpoint. Uma mensagem para 3 endpoints gera 3 entregas, cada uma com seus retries.
- Tentativa: cada chamada HTTP de uma entrega, registrada com status, latência e o começo da resposta.
- Fonte e rota: para receber webhooks de terceiros (Stripe, GitHub…) numa URL da plataforma e redistribuí-los para os seus endpoints. Veja Receber webhooks de terceiros.
Todas as requisições enviadas seguem o padrão Standard Webhooks (headers webhook-id, webhook-timestamp e webhook-signature). O formato, a verificação da assinatura e o calendário de retries estão no guia Webhooks.
Primeiros passos
Crie um endpoint com a URL de destino e os eventos que ele assina. Guarde o secret da resposta (use standard_webhooks_secret com as bibliotecas oficiais). Sem project_id, ele vai para o projeto padrão.
Teste a conexão com o ping de teste: um evento webhook.test grátis.
Envie uma mensagem com event_type e payload. Use X-Dry-Run: true antes para ver quantas entregas ela gera e quanto custa.
Verifique a assinatura no receptor e responda 2xx rápido. Veja os exemplos em Validar a assinatura.
Acompanhe as entregas no log de mensagens e reenvie o que falhou.
Custos
Por entrega — Uma entrega é uma mensagem enviada a um endpoint: uma mensagem para 3 endpoints são 3 entregas. Retries automáticos da mesma entrega não cobram de novo.
| Item | Créditos | Notas |
|---|---|---|
| Entrega além da franquia mensal | 0,002 | mensagens da API, ingestão, retry manual e recuperação |
| Eventos da plataforma e callbacks de jobs | 0,0 | ex.: qr.dynamic.scanned, webhook_url do PDF, ping webhook.test |
Franquia mensal — Por conta e por mês (UTC), somando API e ingestão de todos os projetos. O valor padrão pode ser ajustado pela plataforma; a resposta do envio mostra quantas entregas saíram da franquia (`free_deliveries`) e o uso do mês aparece em `GET /usage`.
| Item | Grátis por mês |
|---|---|
| Entregas | 1.000 |
A cobrança é feita no envio, por mensagem, pelas entregas que passam da franquia (charged_units, em unidades de 1/10.000 de crédito). Se a mensagem não puder ser gravada, o valor é estornado. O payload é validado antes de qualquer cobrança.
A franquia e a cobrança são por conta: todos os projetos dividem a mesma franquia mensal, e uma entrega custa o mesmo em qualquer projeto.
Leituras do log
Configurar projetos, endpoints, tipos de evento, fontes e rotas é grátis. As rotas de listar e consultar mensagens são leituras medidas: entram na franquia diária de leituras e, depois dela, custam o valor da tabela de leituras.
Consultar uso do mês
/v1/tools/webhooks/usage- Custo
- Grátis
- Dry-run
- Não
- Autenticação
- qualquer um de
tools:webhooks:readtools:webhooks:managetools:readtools:manage
Mostra o mês atual (UTC, de period_start a period_end): a franquia (free_deliveries), as entregas já usadas por API e ingestão (used_deliveries) e o que resta (free_left). delivery_units e read_units são os preços atuais de uma entrega e de uma leitura do log, em unidades; blocked_retention_days é quanto tempo uma entrega espera por créditos (veja Sem créditos).
curl -sS -X GET 'https://api.respawntech.dev/v1/tools/webhooks/usage' \
-H "X-API-Token: $RESPAWN_API_TOKEN"const response = await fetch('https://api.respawntech.dev/v1/tools/webhooks/usage', {
method: 'GET',
headers: {
'X-API-Token': process.env.RESPAWN_API_TOKEN,
},
});
if (!response.ok) throw new Error((await response.json()).error.code);
console.log(await response.json());import os
import requests
response = requests.get(
"https://api.respawntech.dev/v1/tools/webhooks/usage",
headers={
"X-API-Token": os.environ["RESPAWN_API_TOKEN"],
},
timeout=30,
)
if not response.ok:
raise RuntimeError(response.json()["error"]["code"])
print(response.json())<?php
$ch = curl_init('https://api.respawntech.dev/v1/tools/webhooks/usage');
curl_setopt_array($ch, [
CURLOPT_CUSTOMREQUEST => 'GET',
CURLOPT_HTTPHEADER => [
'X-API-Token: ' . getenv('RESPAWN_API_TOKEN'),
],
CURLOPT_RETURNTRANSFER => true,
CURLOPT_TIMEOUT => 30,
]);
$body = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_RESPONSE_CODE);
if ($status >= 400) {
throw new RuntimeException(json_decode($body, true)['error']['code']);
}
print_r(json_decode($body, true));package main
import (
"fmt"
"io"
"net/http"
"os"
"time"
)
func main() {
req, err := http.NewRequest("GET", "https://api.respawntech.dev/v1/tools/webhooks/usage", nil)
if err != nil {
panic(err)
}
req.Header.Set("X-API-Token", os.Getenv("RESPAWN_API_TOKEN"))
client := &http.Client{Timeout: 30 * time.Second}
res, err := client.Do(req)
if err != nil {
panic(err)
}
defer res.Body.Close()
data, err := io.ReadAll(res.Body)
if err != nil {
panic(err)
}
if res.StatusCode >= 400 {
panic(string(data))
}
fmt.Println(string(data))
}Resposta 200 — valores da conta inteira, somando todos os projetos; preços em unidades (10.000 = 1 crédito)
{
"success": true,
"trace_id": "550e8400-e29b-41d4-a716-446655440000",
"data": {
"period_start": "2026-09-01T00:00:00Z",
"period_end": "2026-10-01T00:00:00Z",
"free_deliveries": 1000,
"used_deliveries": 340,
"free_left": 660,
"delivery_units": 20,
"read_units": 20,
"blocked_retention_days": 7
}
}Projetos
Tudo o que você configura no Webhook Relay pertence a um projeto: endpoints, fontes (e suas rotas), tipos de evento, mensagens e o layout do canvas no dashboard. Use projetos para separar configurações, por exemplo uma por loja ou por cliente.
- Toda conta tem um projeto padrão (
is_default: true, chamadoDefault), criado no primeiro uso. Ele não pode ser excluído. project_idé opcional em toda a API: sem ele, criações e envios usam o projeto padrão. Integrações que não conhecem projetos continuam funcionando.- Uma mensagem só chega aos endpoints do projeto dela e é validada com os tipos de evento desse projeto. Nomes de tipo de evento são únicos por projeto.
- As listagens (
GET /endpoints,/sources,/event-types,/routese/messages) aceitam?project_id=como filtro; sem ele, trazem todos os projetos. - Eventos da plataforma (ex.:
qr.dynamic.scanned) geram uma mensagem em cada projeto que tem endpoints inscritos, sempre grátis. Callbacks de jobs (webhook_url) não pertencem a nenhum projeto (project_id: null). - Limites e cobrança continuam por conta: veja Limites. Por padrão não há limite de projetos.
Listar projetos
/v1/tools/webhooks/projects- Custo
- Grátis
- Dry-run
- Não
- Autenticação
- qualquer um de
tools:webhooks:readtools:webhooks:managetools:readtools:manage
Paginado e com busca: q procura no nome (sem diferenciar maiúsculas), page começa em 1 e page_size vai até 100 (padrão 50). Cada projeto traz stats: fontes, endpoints, mensagens das últimas 24 horas, entregas do mês e failing_endpoints (endpoints falhando ou desativados automaticamente; um endpoint pausado por você não conta).
curl -sS -X GET 'https://api.respawntech.dev/v1/tools/webhooks/projects?q=loja&page=1&page_size=20' \
-H "X-API-Token: $RESPAWN_API_TOKEN"const response = await fetch('https://api.respawntech.dev/v1/tools/webhooks/projects?q=loja&page=1&page_size=20', {
method: 'GET',
headers: {
'X-API-Token': process.env.RESPAWN_API_TOKEN,
},
});
if (!response.ok) throw new Error((await response.json()).error.code);
console.log(await response.json());import os
import requests
response = requests.get(
"https://api.respawntech.dev/v1/tools/webhooks/projects?q=loja&page=1&page_size=20",
headers={
"X-API-Token": os.environ["RESPAWN_API_TOKEN"],
},
timeout=30,
)
if not response.ok:
raise RuntimeError(response.json()["error"]["code"])
print(response.json())<?php
$ch = curl_init('https://api.respawntech.dev/v1/tools/webhooks/projects?q=loja&page=1&page_size=20');
curl_setopt_array($ch, [
CURLOPT_CUSTOMREQUEST => 'GET',
CURLOPT_HTTPHEADER => [
'X-API-Token: ' . getenv('RESPAWN_API_TOKEN'),
],
CURLOPT_RETURNTRANSFER => true,
CURLOPT_TIMEOUT => 30,
]);
$body = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_RESPONSE_CODE);
if ($status >= 400) {
throw new RuntimeException(json_decode($body, true)['error']['code']);
}
print_r(json_decode($body, true));package main
import (
"fmt"
"io"
"net/http"
"os"
"time"
)
func main() {
req, err := http.NewRequest("GET", "https://api.respawntech.dev/v1/tools/webhooks/projects?q=loja&page=1&page_size=20", nil)
if err != nil {
panic(err)
}
req.Header.Set("X-API-Token", os.Getenv("RESPAWN_API_TOKEN"))
client := &http.Client{Timeout: 30 * time.Second}
res, err := client.Do(req)
if err != nil {
panic(err)
}
defer res.Body.Close()
data, err := io.ReadAll(res.Body)
if err != nil {
panic(err)
}
if res.StatusCode >= 400 {
panic(string(data))
}
fmt.Println(string(data))
}Resposta 200 — o projeto padrão vem primeiro; total conta os projetos que atendem à busca
{
"success": true,
"trace_id": "550e8400-e29b-41d4-a716-446655440000",
"data": {
"projects": [
{
"id": "7a1d4c8e-2f6b-4e9a-b3c5-0d8f1a6e4b27",
"name": "Default",
"description": "",
"is_default": true,
"created_at": "2026-09-25T11:40:00Z",
"updated_at": "2026-09-25T11:40:00Z",
"stats": {
"sources": 0,
"endpoints": 1,
"messages_24h": 3,
"deliveries_month": 57,
"failing_endpoints": 0
}
},
{
"id": "3e7c1a9f-5b2d-4c8e-a6f0-9d1b4e7a2c53",
"name": "Loja",
"description": "Eventos da loja virtual",
"is_default": false,
"created_at": "2026-09-25T11:40:00Z",
"updated_at": "2026-09-25T11:40:00Z",
"stats": {
"sources": 1,
"endpoints": 2,
"messages_24h": 12,
"deliveries_month": 340,
"failing_endpoints": 0
}
}
],
"total": 2,
"page": 1,
"page_size": 50
}
}Criar projeto
/v1/tools/webhooks/projects- Custo
- Grátis
- Dry-run
- Não
- Autenticação
- qualquer um de
tools:webhooks:managetools:manage
name (obrigatório) tem até 80 caracteres; description, até 500.
curl -sS -X POST 'https://api.respawntech.dev/v1/tools/webhooks/projects' \
-H "X-API-Token: $RESPAWN_API_TOKEN" \
-H 'Content-Type: application/json' \
-d '{ "name": "Loja", "description": "Eventos da loja virtual" }'const response = await fetch('https://api.respawntech.dev/v1/tools/webhooks/projects', {
method: 'POST',
headers: {
'X-API-Token': process.env.RESPAWN_API_TOKEN,
'Content-Type': 'application/json',
},
body: JSON.stringify({ "name": "Loja", "description": "Eventos da loja virtual" }),
});
if (!response.ok) throw new Error((await response.json()).error.code);
console.log(await response.json());import os
import requests
response = requests.post(
"https://api.respawntech.dev/v1/tools/webhooks/projects",
headers={
"X-API-Token": os.environ["RESPAWN_API_TOKEN"],
},
json={ "name": "Loja", "description": "Eventos da loja virtual" },
timeout=30,
)
if not response.ok:
raise RuntimeError(response.json()["error"]["code"])
print(response.json())<?php
$ch = curl_init('https://api.respawntech.dev/v1/tools/webhooks/projects');
curl_setopt_array($ch, [
CURLOPT_CUSTOMREQUEST => 'POST',
CURLOPT_HTTPHEADER => [
'X-API-Token: ' . getenv('RESPAWN_API_TOKEN'),
'Content-Type: application/json',
],
CURLOPT_POSTFIELDS => <<<'JSON'
{ "name": "Loja", "description": "Eventos da loja virtual" }
JSON,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_TIMEOUT => 30,
]);
$body = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_RESPONSE_CODE);
if ($status >= 400) {
throw new RuntimeException(json_decode($body, true)['error']['code']);
}
print_r(json_decode($body, true));package main
import (
"fmt"
"io"
"net/http"
"os"
"strings"
"time"
)
func main() {
req, err := http.NewRequest("POST", "https://api.respawntech.dev/v1/tools/webhooks/projects", strings.NewReader(`{ "name": "Loja", "description": "Eventos da loja virtual" }`))
if err != nil {
panic(err)
}
req.Header.Set("X-API-Token", os.Getenv("RESPAWN_API_TOKEN"))
req.Header.Set("Content-Type", "application/json")
client := &http.Client{Timeout: 30 * time.Second}
res, err := client.Do(req)
if err != nil {
panic(err)
}
defer res.Body.Close()
data, err := io.ReadAll(res.Body)
if err != nil {
panic(err)
}
if res.StatusCode >= 400 {
panic(string(data))
}
fmt.Println(string(data))
}Resposta 201
{
"success": true,
"trace_id": "550e8400-e29b-41d4-a716-446655440000",
"data": {
"id": "3e7c1a9f-5b2d-4c8e-a6f0-9d1b4e7a2c53",
"name": "Loja",
"description": "Eventos da loja virtual",
"is_default": false,
"created_at": "2026-09-25T11:40:00Z",
"updated_at": "2026-09-25T11:40:00Z",
"stats": {
"sources": 0,
"endpoints": 0,
"messages_24h": 0,
"deliveries_month": 0,
"failing_endpoints": 0
}
}
}Consultar projeto
/v1/tools/webhooks/projects/{id}- Custo
- Grátis
- Dry-run
- Não
- Autenticação
- qualquer um de
tools:webhooks:readtools:webhooks:managetools:readtools:manage
curl -sS -X GET 'https://api.respawntech.dev/v1/tools/webhooks/projects/550e8400-e29b-41d4-a716-446655440000' \
-H "X-API-Token: $RESPAWN_API_TOKEN"const response = await fetch('https://api.respawntech.dev/v1/tools/webhooks/projects/550e8400-e29b-41d4-a716-446655440000', {
method: 'GET',
headers: {
'X-API-Token': process.env.RESPAWN_API_TOKEN,
},
});
if (!response.ok) throw new Error((await response.json()).error.code);
console.log(await response.json());import os
import requests
response = requests.get(
"https://api.respawntech.dev/v1/tools/webhooks/projects/550e8400-e29b-41d4-a716-446655440000",
headers={
"X-API-Token": os.environ["RESPAWN_API_TOKEN"],
},
timeout=30,
)
if not response.ok:
raise RuntimeError(response.json()["error"]["code"])
print(response.json())<?php
$ch = curl_init('https://api.respawntech.dev/v1/tools/webhooks/projects/550e8400-e29b-41d4-a716-446655440000');
curl_setopt_array($ch, [
CURLOPT_CUSTOMREQUEST => 'GET',
CURLOPT_HTTPHEADER => [
'X-API-Token: ' . getenv('RESPAWN_API_TOKEN'),
],
CURLOPT_RETURNTRANSFER => true,
CURLOPT_TIMEOUT => 30,
]);
$body = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_RESPONSE_CODE);
if ($status >= 400) {
throw new RuntimeException(json_decode($body, true)['error']['code']);
}
print_r(json_decode($body, true));package main
import (
"fmt"
"io"
"net/http"
"os"
"time"
)
func main() {
req, err := http.NewRequest("GET", "https://api.respawntech.dev/v1/tools/webhooks/projects/550e8400-e29b-41d4-a716-446655440000", nil)
if err != nil {
panic(err)
}
req.Header.Set("X-API-Token", os.Getenv("RESPAWN_API_TOKEN"))
client := &http.Client{Timeout: 30 * time.Second}
res, err := client.Do(req)
if err != nil {
panic(err)
}
defer res.Body.Close()
data, err := io.ReadAll(res.Body)
if err != nil {
panic(err)
}
if res.StatusCode >= 400 {
panic(string(data))
}
fmt.Println(string(data))
}Resposta 200
{
"success": true,
"trace_id": "550e8400-e29b-41d4-a716-446655440000",
"data": {
"id": "3e7c1a9f-5b2d-4c8e-a6f0-9d1b4e7a2c53",
"name": "Loja",
"description": "Eventos da loja virtual",
"is_default": false,
"created_at": "2026-09-25T11:40:00Z",
"updated_at": "2026-09-25T11:40:00Z",
"stats": {
"sources": 1,
"endpoints": 2,
"messages_24h": 12,
"deliveries_month": 340,
"failing_endpoints": 0
}
}
}Alterar projeto
/v1/tools/webhooks/projects/{id}- Custo
- Grátis
- Dry-run
- Não
- Autenticação
- qualquer um de
tools:webhooks:managetools:manage
Envie só os campos que quer mudar.
curl -sS -X PATCH 'https://api.respawntech.dev/v1/tools/webhooks/projects/550e8400-e29b-41d4-a716-446655440000' \
-H "X-API-Token: $RESPAWN_API_TOKEN" \
-H 'Content-Type: application/json' \
-d '{ "name": "Loja principal" }'const response = await fetch('https://api.respawntech.dev/v1/tools/webhooks/projects/550e8400-e29b-41d4-a716-446655440000', {
method: 'PATCH',
headers: {
'X-API-Token': process.env.RESPAWN_API_TOKEN,
'Content-Type': 'application/json',
},
body: JSON.stringify({ "name": "Loja principal" }),
});
if (!response.ok) throw new Error((await response.json()).error.code);
console.log(await response.json());import os
import requests
response = requests.patch(
"https://api.respawntech.dev/v1/tools/webhooks/projects/550e8400-e29b-41d4-a716-446655440000",
headers={
"X-API-Token": os.environ["RESPAWN_API_TOKEN"],
},
json={ "name": "Loja principal" },
timeout=30,
)
if not response.ok:
raise RuntimeError(response.json()["error"]["code"])
print(response.json())<?php
$ch = curl_init('https://api.respawntech.dev/v1/tools/webhooks/projects/550e8400-e29b-41d4-a716-446655440000');
curl_setopt_array($ch, [
CURLOPT_CUSTOMREQUEST => 'PATCH',
CURLOPT_HTTPHEADER => [
'X-API-Token: ' . getenv('RESPAWN_API_TOKEN'),
'Content-Type: application/json',
],
CURLOPT_POSTFIELDS => <<<'JSON'
{ "name": "Loja principal" }
JSON,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_TIMEOUT => 30,
]);
$body = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_RESPONSE_CODE);
if ($status >= 400) {
throw new RuntimeException(json_decode($body, true)['error']['code']);
}
print_r(json_decode($body, true));package main
import (
"fmt"
"io"
"net/http"
"os"
"strings"
"time"
)
func main() {
req, err := http.NewRequest("PATCH", "https://api.respawntech.dev/v1/tools/webhooks/projects/550e8400-e29b-41d4-a716-446655440000", strings.NewReader(`{ "name": "Loja principal" }`))
if err != nil {
panic(err)
}
req.Header.Set("X-API-Token", os.Getenv("RESPAWN_API_TOKEN"))
req.Header.Set("Content-Type", "application/json")
client := &http.Client{Timeout: 30 * time.Second}
res, err := client.Do(req)
if err != nil {
panic(err)
}
defer res.Body.Close()
data, err := io.ReadAll(res.Body)
if err != nil {
panic(err)
}
if res.StatusCode >= 400 {
panic(string(data))
}
fmt.Println(string(data))
}Resposta 200
{
"success": true,
"trace_id": "550e8400-e29b-41d4-a716-446655440000",
"data": {
"id": "3e7c1a9f-5b2d-4c8e-a6f0-9d1b4e7a2c53",
"name": "Loja principal",
"description": "Eventos da loja virtual",
"is_default": false,
"created_at": "2026-09-25T11:40:00Z",
"updated_at": "2026-09-25T13:00:00Z",
"stats": {
"sources": 1,
"endpoints": 2,
"messages_24h": 12,
"deliveries_month": 340,
"failing_endpoints": 0
}
}
}Excluir projeto
/v1/tools/webhooks/projects/{id}- Custo
- Grátis
- Dry-run
- Não
- Autenticação
- qualquer um de
tools:webhooks:managetools:manage
Exclui os endpoints do projeto (as entregas pendentes são canceladas), as fontes, as rotas, os tipos de evento e o layout. As mensagens continuam no log geral, com project_id: null.
curl -sS -X DELETE 'https://api.respawntech.dev/v1/tools/webhooks/projects/550e8400-e29b-41d4-a716-446655440000' \
-H "X-API-Token: $RESPAWN_API_TOKEN"const response = await fetch('https://api.respawntech.dev/v1/tools/webhooks/projects/550e8400-e29b-41d4-a716-446655440000', {
method: 'DELETE',
headers: {
'X-API-Token': process.env.RESPAWN_API_TOKEN,
},
});
if (!response.ok) throw new Error((await response.json()).error.code);
console.log(await response.json());import os
import requests
response = requests.delete(
"https://api.respawntech.dev/v1/tools/webhooks/projects/550e8400-e29b-41d4-a716-446655440000",
headers={
"X-API-Token": os.environ["RESPAWN_API_TOKEN"],
},
timeout=30,
)
if not response.ok:
raise RuntimeError(response.json()["error"]["code"])
print(response.json())<?php
$ch = curl_init('https://api.respawntech.dev/v1/tools/webhooks/projects/550e8400-e29b-41d4-a716-446655440000');
curl_setopt_array($ch, [
CURLOPT_CUSTOMREQUEST => 'DELETE',
CURLOPT_HTTPHEADER => [
'X-API-Token: ' . getenv('RESPAWN_API_TOKEN'),
],
CURLOPT_RETURNTRANSFER => true,
CURLOPT_TIMEOUT => 30,
]);
$body = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_RESPONSE_CODE);
if ($status >= 400) {
throw new RuntimeException(json_decode($body, true)['error']['code']);
}
print_r(json_decode($body, true));package main
import (
"fmt"
"io"
"net/http"
"os"
"time"
)
func main() {
req, err := http.NewRequest("DELETE", "https://api.respawntech.dev/v1/tools/webhooks/projects/550e8400-e29b-41d4-a716-446655440000", nil)
if err != nil {
panic(err)
}
req.Header.Set("X-API-Token", os.Getenv("RESPAWN_API_TOKEN"))
client := &http.Client{Timeout: 30 * time.Second}
res, err := client.Do(req)
if err != nil {
panic(err)
}
defer res.Body.Close()
data, err := io.ReadAll(res.Body)
if err != nil {
panic(err)
}
if res.StatusCode >= 400 {
panic(string(data))
}
fmt.Println(string(data))
}Resposta 200 — o projeto padrão não pode ser excluído (400)
{
"success": true,
"trace_id": "550e8400-e29b-41d4-a716-446655440000",
"data": {
"deleted": true
}
}Duplicar projeto
/v1/tools/webhooks/projects/{id}/duplicate- Custo
- Grátis
- Dry-run
- Não
- Autenticação
- qualquer um de
tools:webhooks:managetools:manage
Cria um projeto novo com a mesma descrição e cópias de tudo, já ligadas entre si:
- Endpoints com secrets novos (mesma URL, eventos, headers, limite de envio, formato e estado ativo; o histórico de falhas não é copiado).
- Fontes com
ingest_urlnovas (mesmas configurações e o mesmo secret do provedor). Cadastre as URLs novas no provedor. - Rotas, tipos de evento e o layout do canvas.
Mensagens, entregas e contadores não são copiados. Os limites da conta são conferidos antes: se a cópia passaria de algum, a resposta é webhook.limit_reached e nada é criado.
curl -sS -X POST 'https://api.respawntech.dev/v1/tools/webhooks/projects/550e8400-e29b-41d4-a716-446655440000/duplicate' \
-H "X-API-Token: $RESPAWN_API_TOKEN" \
-H 'Content-Type: application/json' \
-d '{ "name": "Loja (cópia)" }'const response = await fetch('https://api.respawntech.dev/v1/tools/webhooks/projects/550e8400-e29b-41d4-a716-446655440000/duplicate', {
method: 'POST',
headers: {
'X-API-Token': process.env.RESPAWN_API_TOKEN,
'Content-Type': 'application/json',
},
body: JSON.stringify({ "name": "Loja (cópia)" }),
});
if (!response.ok) throw new Error((await response.json()).error.code);
console.log(await response.json());import os
import requests
response = requests.post(
"https://api.respawntech.dev/v1/tools/webhooks/projects/550e8400-e29b-41d4-a716-446655440000/duplicate",
headers={
"X-API-Token": os.environ["RESPAWN_API_TOKEN"],
},
json={ "name": "Loja (cópia)" },
timeout=30,
)
if not response.ok:
raise RuntimeError(response.json()["error"]["code"])
print(response.json())<?php
$ch = curl_init('https://api.respawntech.dev/v1/tools/webhooks/projects/550e8400-e29b-41d4-a716-446655440000/duplicate');
curl_setopt_array($ch, [
CURLOPT_CUSTOMREQUEST => 'POST',
CURLOPT_HTTPHEADER => [
'X-API-Token: ' . getenv('RESPAWN_API_TOKEN'),
'Content-Type: application/json',
],
CURLOPT_POSTFIELDS => <<<'JSON'
{ "name": "Loja (cópia)" }
JSON,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_TIMEOUT => 30,
]);
$body = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_RESPONSE_CODE);
if ($status >= 400) {
throw new RuntimeException(json_decode($body, true)['error']['code']);
}
print_r(json_decode($body, true));package main
import (
"fmt"
"io"
"net/http"
"os"
"strings"
"time"
)
func main() {
req, err := http.NewRequest("POST", "https://api.respawntech.dev/v1/tools/webhooks/projects/550e8400-e29b-41d4-a716-446655440000/duplicate", strings.NewReader(`{ "name": "Loja (cópia)" }`))
if err != nil {
panic(err)
}
req.Header.Set("X-API-Token", os.Getenv("RESPAWN_API_TOKEN"))
req.Header.Set("Content-Type", "application/json")
client := &http.Client{Timeout: 30 * time.Second}
res, err := client.Do(req)
if err != nil {
panic(err)
}
defer res.Body.Close()
data, err := io.ReadAll(res.Body)
if err != nil {
panic(err)
}
if res.StatusCode >= 400 {
panic(string(data))
}
fmt.Println(string(data))
}Resposta 201 — endpoints com secrets novos e fontes com ingest_url novas; mensagens e contadores não são copiados
{
"success": true,
"trace_id": "550e8400-e29b-41d4-a716-446655440000",
"data": {
"id": "b8e2f5a1-4c7d-4a3e-9f6b-2d1c8e5a7f40",
"name": "Loja (cópia)",
"description": "Eventos da loja virtual",
"is_default": false,
"created_at": "2026-09-25T14:00:00Z",
"updated_at": "2026-09-25T14:00:00Z",
"stats": {
"sources": 1,
"endpoints": 2,
"messages_24h": 0,
"deliveries_month": 0,
"failing_endpoints": 0
}
}
}Endpoints
Criar endpoint
/v1/tools/webhooks/endpoints- Custo
- Grátis
- Dry-run
- Não
- Autenticação
- qualquer um de
tools:webhooks:managetools:manage
url(obrigatório): URL públicahttpouhttps. Endereços privados, locais ou de metadados de nuvem são recusados (webhook.invalid_url).events: tipos de evento que o endpoint recebe das mensagens da API e da plataforma;["*"]assina todos. Sem eventos, o endpoint só recebe o que as rotas mandam para ele.project_id: projeto do endpoint (padrão: o projeto padrão). Um endpoint não muda de projeto.format:raw(padrão),slack,discordouteams. Veja Slack, Discord e Teams.custom_headers: até 20 headers extras enviados em toda entrega (ex.: um token do seu cliente). Headers de assinatura,Content-Type,User-AgenteHostnão podem ser sobrescritos.rate_limit_per_sec: teto de envios por segundo para este endpoint (0= sem teto próprio).- O
secretvem só nesta resposta; depois, use Revelar secret. Até 200 endpoints por conta, somando todos os projetos.
curl -sS -X POST 'https://api.respawntech.dev/v1/tools/webhooks/endpoints' \
-H "X-API-Token: $RESPAWN_API_TOKEN" \
-H 'Content-Type: application/json' \
-d '{
"url": "https://api.example.com/hooks/respawn",
"description": "Pedidos da loja",
"events": ["order.paid", "order.refunded"],
"custom_headers": { "X-Tenant": "loja-1" }
}'const response = await fetch('https://api.respawntech.dev/v1/tools/webhooks/endpoints', {
method: 'POST',
headers: {
'X-API-Token': process.env.RESPAWN_API_TOKEN,
'Content-Type': 'application/json',
},
body: JSON.stringify({
"url": "https://api.example.com/hooks/respawn",
"description": "Pedidos da loja",
"events": ["order.paid", "order.refunded"],
"custom_headers": { "X-Tenant": "loja-1" }
}),
});
if (!response.ok) throw new Error((await response.json()).error.code);
console.log(await response.json());import os
import requests
response = requests.post(
"https://api.respawntech.dev/v1/tools/webhooks/endpoints",
headers={
"X-API-Token": os.environ["RESPAWN_API_TOKEN"],
},
json={
"url": "https://api.example.com/hooks/respawn",
"description": "Pedidos da loja",
"events": ["order.paid", "order.refunded"],
"custom_headers": { "X-Tenant": "loja-1" }
},
timeout=30,
)
if not response.ok:
raise RuntimeError(response.json()["error"]["code"])
print(response.json())<?php
$ch = curl_init('https://api.respawntech.dev/v1/tools/webhooks/endpoints');
curl_setopt_array($ch, [
CURLOPT_CUSTOMREQUEST => 'POST',
CURLOPT_HTTPHEADER => [
'X-API-Token: ' . getenv('RESPAWN_API_TOKEN'),
'Content-Type: application/json',
],
CURLOPT_POSTFIELDS => <<<'JSON'
{
"url": "https://api.example.com/hooks/respawn",
"description": "Pedidos da loja",
"events": ["order.paid", "order.refunded"],
"custom_headers": { "X-Tenant": "loja-1" }
}
JSON,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_TIMEOUT => 30,
]);
$body = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_RESPONSE_CODE);
if ($status >= 400) {
throw new RuntimeException(json_decode($body, true)['error']['code']);
}
print_r(json_decode($body, true));package main
import (
"fmt"
"io"
"net/http"
"os"
"strings"
"time"
)
func main() {
req, err := http.NewRequest("POST", "https://api.respawntech.dev/v1/tools/webhooks/endpoints", strings.NewReader(`{
"url": "https://api.example.com/hooks/respawn",
"description": "Pedidos da loja",
"events": ["order.paid", "order.refunded"],
"custom_headers": { "X-Tenant": "loja-1" }
}`))
if err != nil {
panic(err)
}
req.Header.Set("X-API-Token", os.Getenv("RESPAWN_API_TOKEN"))
req.Header.Set("Content-Type", "application/json")
client := &http.Client{Timeout: 30 * time.Second}
res, err := client.Do(req)
if err != nil {
panic(err)
}
defer res.Body.Close()
data, err := io.ReadAll(res.Body)
if err != nil {
panic(err)
}
if res.StatusCode >= 400 {
panic(string(data))
}
fmt.Println(string(data))
}Resposta 201 — o secret só aparece aqui e na rota de revelar o secret
{
"success": true,
"trace_id": "550e8400-e29b-41d4-a716-446655440000",
"data": {
"endpoint": {
"id": "8d2f4a61-3c7b-4e9a-b1d5-6f0e2a9c7b34",
"project_id": "3e7c1a9f-5b2d-4c8e-a6f0-9d1b4e7a2c53",
"url": "https://api.example.com/hooks/respawn",
"description": "Pedidos da loja",
"events": [
"order.paid",
"order.refunded"
],
"custom_headers": {
"X-Tenant": "loja-1"
},
"is_active": true,
"rate_limit_per_sec": 0,
"format": "raw",
"created_at": "2026-09-25T12:00:00Z",
"updated_at": "2026-09-25T12:00:00Z"
},
"secret": {
"secret": "9f2c4e6a8b0d1f3e5a7c9e1b3d5f7a9c2e4b6d8f0a1c3e5b7d9f1a3c5e7b9d0f",
"standard_webhooks_secret": "whsec_OWYyYzRlNmE4YjBkMWYzZTVhN2M5ZTFiM2Q1ZjdhOWMyZTRiNmQ4ZjBhMWMzZTViN2Q5ZjFhM2M1ZTdiOWQwZg=="
}
}
}Listar endpoints
/v1/tools/webhooks/endpoints- Custo
- Grátis
- Dry-run
- Não
- Autenticação
- qualquer um de
tools:webhooks:readtools:webhooks:managetools:readtools:manage
Filtro opcional por project_id. Um endpoint desativado automaticamente traz is_active: false e disabled_reason; um que está falhando traz failing_since.
curl -sS -X GET 'https://api.respawntech.dev/v1/tools/webhooks/endpoints?project_id=3e7c1a9f-5b2d-4c8e-a6f0-9d1b4e7a2c53' \
-H "X-API-Token: $RESPAWN_API_TOKEN"const response = await fetch('https://api.respawntech.dev/v1/tools/webhooks/endpoints?project_id=3e7c1a9f-5b2d-4c8e-a6f0-9d1b4e7a2c53', {
method: 'GET',
headers: {
'X-API-Token': process.env.RESPAWN_API_TOKEN,
},
});
if (!response.ok) throw new Error((await response.json()).error.code);
console.log(await response.json());import os
import requests
response = requests.get(
"https://api.respawntech.dev/v1/tools/webhooks/endpoints?project_id=3e7c1a9f-5b2d-4c8e-a6f0-9d1b4e7a2c53",
headers={
"X-API-Token": os.environ["RESPAWN_API_TOKEN"],
},
timeout=30,
)
if not response.ok:
raise RuntimeError(response.json()["error"]["code"])
print(response.json())<?php
$ch = curl_init('https://api.respawntech.dev/v1/tools/webhooks/endpoints?project_id=3e7c1a9f-5b2d-4c8e-a6f0-9d1b4e7a2c53');
curl_setopt_array($ch, [
CURLOPT_CUSTOMREQUEST => 'GET',
CURLOPT_HTTPHEADER => [
'X-API-Token: ' . getenv('RESPAWN_API_TOKEN'),
],
CURLOPT_RETURNTRANSFER => true,
CURLOPT_TIMEOUT => 30,
]);
$body = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_RESPONSE_CODE);
if ($status >= 400) {
throw new RuntimeException(json_decode($body, true)['error']['code']);
}
print_r(json_decode($body, true));package main
import (
"fmt"
"io"
"net/http"
"os"
"time"
)
func main() {
req, err := http.NewRequest("GET", "https://api.respawntech.dev/v1/tools/webhooks/endpoints?project_id=3e7c1a9f-5b2d-4c8e-a6f0-9d1b4e7a2c53", nil)
if err != nil {
panic(err)
}
req.Header.Set("X-API-Token", os.Getenv("RESPAWN_API_TOKEN"))
client := &http.Client{Timeout: 30 * time.Second}
res, err := client.Do(req)
if err != nil {
panic(err)
}
defer res.Body.Close()
data, err := io.ReadAll(res.Body)
if err != nil {
panic(err)
}
if res.StatusCode >= 400 {
panic(string(data))
}
fmt.Println(string(data))
}Resposta 200
{
"success": true,
"trace_id": "550e8400-e29b-41d4-a716-446655440000",
"data": {
"endpoints": [
{
"id": "8d2f4a61-3c7b-4e9a-b1d5-6f0e2a9c7b34",
"project_id": "3e7c1a9f-5b2d-4c8e-a6f0-9d1b4e7a2c53",
"url": "https://api.example.com/hooks/respawn",
"description": "Pedidos da loja",
"events": [
"order.paid",
"order.refunded"
],
"custom_headers": {
"X-Tenant": "loja-1"
},
"is_active": true,
"rate_limit_per_sec": 0,
"format": "raw",
"created_at": "2026-09-25T12:00:00Z",
"updated_at": "2026-09-25T12:00:00Z"
}
]
}
}Consultar endpoint
/v1/tools/webhooks/endpoints/{id}- Custo
- Grátis
- Dry-run
- Não
- Autenticação
- qualquer um de
tools:webhooks:readtools:webhooks:managetools:readtools:manage
curl -sS -X GET 'https://api.respawntech.dev/v1/tools/webhooks/endpoints/550e8400-e29b-41d4-a716-446655440000' \
-H "X-API-Token: $RESPAWN_API_TOKEN"const response = await fetch('https://api.respawntech.dev/v1/tools/webhooks/endpoints/550e8400-e29b-41d4-a716-446655440000', {
method: 'GET',
headers: {
'X-API-Token': process.env.RESPAWN_API_TOKEN,
},
});
if (!response.ok) throw new Error((await response.json()).error.code);
console.log(await response.json());import os
import requests
response = requests.get(
"https://api.respawntech.dev/v1/tools/webhooks/endpoints/550e8400-e29b-41d4-a716-446655440000",
headers={
"X-API-Token": os.environ["RESPAWN_API_TOKEN"],
},
timeout=30,
)
if not response.ok:
raise RuntimeError(response.json()["error"]["code"])
print(response.json())<?php
$ch = curl_init('https://api.respawntech.dev/v1/tools/webhooks/endpoints/550e8400-e29b-41d4-a716-446655440000');
curl_setopt_array($ch, [
CURLOPT_CUSTOMREQUEST => 'GET',
CURLOPT_HTTPHEADER => [
'X-API-Token: ' . getenv('RESPAWN_API_TOKEN'),
],
CURLOPT_RETURNTRANSFER => true,
CURLOPT_TIMEOUT => 30,
]);
$body = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_RESPONSE_CODE);
if ($status >= 400) {
throw new RuntimeException(json_decode($body, true)['error']['code']);
}
print_r(json_decode($body, true));package main
import (
"fmt"
"io"
"net/http"
"os"
"time"
)
func main() {
req, err := http.NewRequest("GET", "https://api.respawntech.dev/v1/tools/webhooks/endpoints/550e8400-e29b-41d4-a716-446655440000", nil)
if err != nil {
panic(err)
}
req.Header.Set("X-API-Token", os.Getenv("RESPAWN_API_TOKEN"))
client := &http.Client{Timeout: 30 * time.Second}
res, err := client.Do(req)
if err != nil {
panic(err)
}
defer res.Body.Close()
data, err := io.ReadAll(res.Body)
if err != nil {
panic(err)
}
if res.StatusCode >= 400 {
panic(string(data))
}
fmt.Println(string(data))
}Resposta 200
{
"success": true,
"trace_id": "550e8400-e29b-41d4-a716-446655440000",
"data": {
"id": "8d2f4a61-3c7b-4e9a-b1d5-6f0e2a9c7b34",
"project_id": "3e7c1a9f-5b2d-4c8e-a6f0-9d1b4e7a2c53",
"url": "https://api.example.com/hooks/respawn",
"description": "Pedidos da loja",
"events": [
"order.paid",
"order.refunded"
],
"custom_headers": {
"X-Tenant": "loja-1"
},
"is_active": true,
"rate_limit_per_sec": 0,
"format": "raw",
"created_at": "2026-09-25T12:00:00Z",
"updated_at": "2026-09-25T12:00:00Z"
}
}Alterar endpoint
/v1/tools/webhooks/endpoints/{id}- Custo
- Grátis
- Dry-run
- Não
- Autenticação
- qualquer um de
tools:webhooks:managetools:manage
Envie só os campos que quer mudar (inclusive format). is_active: true reativa um endpoint desativado e zera a sequência de falhas.
curl -sS -X PATCH 'https://api.respawntech.dev/v1/tools/webhooks/endpoints/550e8400-e29b-41d4-a716-446655440000' \
-H "X-API-Token: $RESPAWN_API_TOKEN" \
-H 'Content-Type: application/json' \
-d '{ "events": ["*"], "is_active": true }'const response = await fetch('https://api.respawntech.dev/v1/tools/webhooks/endpoints/550e8400-e29b-41d4-a716-446655440000', {
method: 'PATCH',
headers: {
'X-API-Token': process.env.RESPAWN_API_TOKEN,
'Content-Type': 'application/json',
},
body: JSON.stringify({ "events": ["*"], "is_active": true }),
});
if (!response.ok) throw new Error((await response.json()).error.code);
console.log(await response.json());import os
import requests
response = requests.patch(
"https://api.respawntech.dev/v1/tools/webhooks/endpoints/550e8400-e29b-41d4-a716-446655440000",
headers={
"X-API-Token": os.environ["RESPAWN_API_TOKEN"],
},
json={ "events": ["*"], "is_active": True },
timeout=30,
)
if not response.ok:
raise RuntimeError(response.json()["error"]["code"])
print(response.json())<?php
$ch = curl_init('https://api.respawntech.dev/v1/tools/webhooks/endpoints/550e8400-e29b-41d4-a716-446655440000');
curl_setopt_array($ch, [
CURLOPT_CUSTOMREQUEST => 'PATCH',
CURLOPT_HTTPHEADER => [
'X-API-Token: ' . getenv('RESPAWN_API_TOKEN'),
'Content-Type: application/json',
],
CURLOPT_POSTFIELDS => <<<'JSON'
{ "events": ["*"], "is_active": true }
JSON,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_TIMEOUT => 30,
]);
$body = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_RESPONSE_CODE);
if ($status >= 400) {
throw new RuntimeException(json_decode($body, true)['error']['code']);
}
print_r(json_decode($body, true));package main
import (
"fmt"
"io"
"net/http"
"os"
"strings"
"time"
)
func main() {
req, err := http.NewRequest("PATCH", "https://api.respawntech.dev/v1/tools/webhooks/endpoints/550e8400-e29b-41d4-a716-446655440000", strings.NewReader(`{ "events": ["*"], "is_active": true }`))
if err != nil {
panic(err)
}
req.Header.Set("X-API-Token", os.Getenv("RESPAWN_API_TOKEN"))
req.Header.Set("Content-Type", "application/json")
client := &http.Client{Timeout: 30 * time.Second}
res, err := client.Do(req)
if err != nil {
panic(err)
}
defer res.Body.Close()
data, err := io.ReadAll(res.Body)
if err != nil {
panic(err)
}
if res.StatusCode >= 400 {
panic(string(data))
}
fmt.Println(string(data))
}Resposta 200
{
"success": true,
"trace_id": "550e8400-e29b-41d4-a716-446655440000",
"data": {
"id": "8d2f4a61-3c7b-4e9a-b1d5-6f0e2a9c7b34",
"project_id": "3e7c1a9f-5b2d-4c8e-a6f0-9d1b4e7a2c53",
"url": "https://api.example.com/hooks/respawn",
"description": "Pedidos da loja",
"events": [
"*"
],
"custom_headers": {
"X-Tenant": "loja-1"
},
"is_active": true,
"rate_limit_per_sec": 0,
"format": "raw",
"created_at": "2026-09-25T12:00:00Z",
"updated_at": "2026-09-25T13:00:00Z"
}
}Excluir endpoint
/v1/tools/webhooks/endpoints/{id}- Custo
- Grátis
- Dry-run
- Não
- Autenticação
- qualquer um de
tools:webhooks:managetools:manage
As entregas pendentes para o endpoint são canceladas.
curl -sS -X DELETE 'https://api.respawntech.dev/v1/tools/webhooks/endpoints/550e8400-e29b-41d4-a716-446655440000' \
-H "X-API-Token: $RESPAWN_API_TOKEN"const response = await fetch('https://api.respawntech.dev/v1/tools/webhooks/endpoints/550e8400-e29b-41d4-a716-446655440000', {
method: 'DELETE',
headers: {
'X-API-Token': process.env.RESPAWN_API_TOKEN,
},
});
if (!response.ok) throw new Error((await response.json()).error.code);
console.log(await response.json());import os
import requests
response = requests.delete(
"https://api.respawntech.dev/v1/tools/webhooks/endpoints/550e8400-e29b-41d4-a716-446655440000",
headers={
"X-API-Token": os.environ["RESPAWN_API_TOKEN"],
},
timeout=30,
)
if not response.ok:
raise RuntimeError(response.json()["error"]["code"])
print(response.json())<?php
$ch = curl_init('https://api.respawntech.dev/v1/tools/webhooks/endpoints/550e8400-e29b-41d4-a716-446655440000');
curl_setopt_array($ch, [
CURLOPT_CUSTOMREQUEST => 'DELETE',
CURLOPT_HTTPHEADER => [
'X-API-Token: ' . getenv('RESPAWN_API_TOKEN'),
],
CURLOPT_RETURNTRANSFER => true,
CURLOPT_TIMEOUT => 30,
]);
$body = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_RESPONSE_CODE);
if ($status >= 400) {
throw new RuntimeException(json_decode($body, true)['error']['code']);
}
print_r(json_decode($body, true));package main
import (
"fmt"
"io"
"net/http"
"os"
"time"
)
func main() {
req, err := http.NewRequest("DELETE", "https://api.respawntech.dev/v1/tools/webhooks/endpoints/550e8400-e29b-41d4-a716-446655440000", nil)
if err != nil {
panic(err)
}
req.Header.Set("X-API-Token", os.Getenv("RESPAWN_API_TOKEN"))
client := &http.Client{Timeout: 30 * time.Second}
res, err := client.Do(req)
if err != nil {
panic(err)
}
defer res.Body.Close()
data, err := io.ReadAll(res.Body)
if err != nil {
panic(err)
}
if res.StatusCode >= 400 {
panic(string(data))
}
fmt.Println(string(data))
}Resposta 200
{
"success": true,
"trace_id": "550e8400-e29b-41d4-a716-446655440000",
"data": {
"deleted": true
}
}Revelar secret
/v1/tools/webhooks/endpoints/{id}/secret- Custo
- Grátis
- Dry-run
- Não
- Autenticação
- qualquer um de
tools:webhooks:managetools:manage
secret é a chave bruta (HMAC do header legado X-Webhook-Signature); standard_webhooks_secret é a mesma chave no formato whsec_… aceito pelas bibliotecas Standard Webhooks.
curl -sS -X GET 'https://api.respawntech.dev/v1/tools/webhooks/endpoints/550e8400-e29b-41d4-a716-446655440000/secret' \
-H "X-API-Token: $RESPAWN_API_TOKEN"const response = await fetch('https://api.respawntech.dev/v1/tools/webhooks/endpoints/550e8400-e29b-41d4-a716-446655440000/secret', {
method: 'GET',
headers: {
'X-API-Token': process.env.RESPAWN_API_TOKEN,
},
});
if (!response.ok) throw new Error((await response.json()).error.code);
console.log(await response.json());import os
import requests
response = requests.get(
"https://api.respawntech.dev/v1/tools/webhooks/endpoints/550e8400-e29b-41d4-a716-446655440000/secret",
headers={
"X-API-Token": os.environ["RESPAWN_API_TOKEN"],
},
timeout=30,
)
if not response.ok:
raise RuntimeError(response.json()["error"]["code"])
print(response.json())<?php
$ch = curl_init('https://api.respawntech.dev/v1/tools/webhooks/endpoints/550e8400-e29b-41d4-a716-446655440000/secret');
curl_setopt_array($ch, [
CURLOPT_CUSTOMREQUEST => 'GET',
CURLOPT_HTTPHEADER => [
'X-API-Token: ' . getenv('RESPAWN_API_TOKEN'),
],
CURLOPT_RETURNTRANSFER => true,
CURLOPT_TIMEOUT => 30,
]);
$body = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_RESPONSE_CODE);
if ($status >= 400) {
throw new RuntimeException(json_decode($body, true)['error']['code']);
}
print_r(json_decode($body, true));package main
import (
"fmt"
"io"
"net/http"
"os"
"time"
)
func main() {
req, err := http.NewRequest("GET", "https://api.respawntech.dev/v1/tools/webhooks/endpoints/550e8400-e29b-41d4-a716-446655440000/secret", nil)
if err != nil {
panic(err)
}
req.Header.Set("X-API-Token", os.Getenv("RESPAWN_API_TOKEN"))
client := &http.Client{Timeout: 30 * time.Second}
res, err := client.Do(req)
if err != nil {
panic(err)
}
defer res.Body.Close()
data, err := io.ReadAll(res.Body)
if err != nil {
panic(err)
}
if res.StatusCode >= 400 {
panic(string(data))
}
fmt.Println(string(data))
}Resposta 200
{
"success": true,
"trace_id": "550e8400-e29b-41d4-a716-446655440000",
"data": {
"secret": "9f2c4e6a8b0d1f3e5a7c9e1b3d5f7a9c2e4b6d8f0a1c3e5b7d9f1a3c5e7b9d0f",
"standard_webhooks_secret": "whsec_OWYyYzRlNmE4YjBkMWYzZTVhN2M5ZTFiM2Q1ZjdhOWMyZTRiNmQ4ZjBhMWMzZTViN2Q5ZjFhM2M1ZTdiOWQwZg=="
}
}Trocar secret
/v1/tools/webhooks/endpoints/{id}/rotate-secret- Custo
- Grátis
- Dry-run
- Não
- Autenticação
- qualquer um de
tools:webhooks:managetools:manage
Durante 24 horas as entregas levam duas assinaturas em webhook-signature (secret novo e antigo), para você trocar o secret do receptor sem perder eventos.
curl -sS -X POST 'https://api.respawntech.dev/v1/tools/webhooks/endpoints/550e8400-e29b-41d4-a716-446655440000/rotate-secret' \
-H "X-API-Token: $RESPAWN_API_TOKEN"const response = await fetch('https://api.respawntech.dev/v1/tools/webhooks/endpoints/550e8400-e29b-41d4-a716-446655440000/rotate-secret', {
method: 'POST',
headers: {
'X-API-Token': process.env.RESPAWN_API_TOKEN,
},
});
if (!response.ok) throw new Error((await response.json()).error.code);
console.log(await response.json());import os
import requests
response = requests.post(
"https://api.respawntech.dev/v1/tools/webhooks/endpoints/550e8400-e29b-41d4-a716-446655440000/rotate-secret",
headers={
"X-API-Token": os.environ["RESPAWN_API_TOKEN"],
},
timeout=30,
)
if not response.ok:
raise RuntimeError(response.json()["error"]["code"])
print(response.json())<?php
$ch = curl_init('https://api.respawntech.dev/v1/tools/webhooks/endpoints/550e8400-e29b-41d4-a716-446655440000/rotate-secret');
curl_setopt_array($ch, [
CURLOPT_CUSTOMREQUEST => 'POST',
CURLOPT_HTTPHEADER => [
'X-API-Token: ' . getenv('RESPAWN_API_TOKEN'),
],
CURLOPT_RETURNTRANSFER => true,
CURLOPT_TIMEOUT => 30,
]);
$body = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_RESPONSE_CODE);
if ($status >= 400) {
throw new RuntimeException(json_decode($body, true)['error']['code']);
}
print_r(json_decode($body, true));package main
import (
"fmt"
"io"
"net/http"
"os"
"time"
)
func main() {
req, err := http.NewRequest("POST", "https://api.respawntech.dev/v1/tools/webhooks/endpoints/550e8400-e29b-41d4-a716-446655440000/rotate-secret", nil)
if err != nil {
panic(err)
}
req.Header.Set("X-API-Token", os.Getenv("RESPAWN_API_TOKEN"))
client := &http.Client{Timeout: 30 * time.Second}
res, err := client.Do(req)
if err != nil {
panic(err)
}
defer res.Body.Close()
data, err := io.ReadAll(res.Body)
if err != nil {
panic(err)
}
if res.StatusCode >= 400 {
panic(string(data))
}
fmt.Println(string(data))
}Resposta 200 — o secret anterior continua assinando por 24 horas
{
"success": true,
"trace_id": "550e8400-e29b-41d4-a716-446655440000",
"data": {
"secret": "4a6c8e0b2d4f6a8c0e2b4d6f8a0c2e4b6d8f0a2c4e6b8d0f2a4c6e8b0d2f4a6c",
"standard_webhooks_secret": "whsec_NGE2YzhlMGIyZDRmNmE4YzBlMmI0ZDZmOGEwYzJlNGI2ZDhmMGEyYzRlNmI4ZDBmMmE0YzZlOGIwZDJmNGE2Yw=="
}
}Enviar ping de teste
/v1/tools/webhooks/endpoints/{id}/test- Custo
- Grátis
- Dry-run
- Não
- Autenticação
- qualquer um de
tools:webhooks:managetools:manage
Envia um evento webhook.test assinado, grátis e fora da franquia. Acompanhe o resultado em Consultar mensagem com o message_id devolvido.
curl -sS -X POST 'https://api.respawntech.dev/v1/tools/webhooks/endpoints/550e8400-e29b-41d4-a716-446655440000/test' \
-H "X-API-Token: $RESPAWN_API_TOKEN"const response = await fetch('https://api.respawntech.dev/v1/tools/webhooks/endpoints/550e8400-e29b-41d4-a716-446655440000/test', {
method: 'POST',
headers: {
'X-API-Token': process.env.RESPAWN_API_TOKEN,
},
});
if (!response.ok) throw new Error((await response.json()).error.code);
console.log(await response.json());import os
import requests
response = requests.post(
"https://api.respawntech.dev/v1/tools/webhooks/endpoints/550e8400-e29b-41d4-a716-446655440000/test",
headers={
"X-API-Token": os.environ["RESPAWN_API_TOKEN"],
},
timeout=30,
)
if not response.ok:
raise RuntimeError(response.json()["error"]["code"])
print(response.json())<?php
$ch = curl_init('https://api.respawntech.dev/v1/tools/webhooks/endpoints/550e8400-e29b-41d4-a716-446655440000/test');
curl_setopt_array($ch, [
CURLOPT_CUSTOMREQUEST => 'POST',
CURLOPT_HTTPHEADER => [
'X-API-Token: ' . getenv('RESPAWN_API_TOKEN'),
],
CURLOPT_RETURNTRANSFER => true,
CURLOPT_TIMEOUT => 30,
]);
$body = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_RESPONSE_CODE);
if ($status >= 400) {
throw new RuntimeException(json_decode($body, true)['error']['code']);
}
print_r(json_decode($body, true));package main
import (
"fmt"
"io"
"net/http"
"os"
"time"
)
func main() {
req, err := http.NewRequest("POST", "https://api.respawntech.dev/v1/tools/webhooks/endpoints/550e8400-e29b-41d4-a716-446655440000/test", nil)
if err != nil {
panic(err)
}
req.Header.Set("X-API-Token", os.Getenv("RESPAWN_API_TOKEN"))
client := &http.Client{Timeout: 30 * time.Second}
res, err := client.Do(req)
if err != nil {
panic(err)
}
defer res.Body.Close()
data, err := io.ReadAll(res.Body)
if err != nil {
panic(err)
}
if res.StatusCode >= 400 {
panic(string(data))
}
fmt.Println(string(data))
}Resposta 202
{
"success": true,
"trace_id": "550e8400-e29b-41d4-a716-446655440000",
"data": {
"message_id": "c1a7e5d3-9b2f-4a6e-8d4c-0f3b7e1a5c92"
}
}Reenviar entregas com falha
/v1/tools/webhooks/endpoints/{id}/recover- Custo
- Cada entrega reenviada é cobrada como uma nova (conta na franquia mensal)
- Dry-run
- Não
- Autenticação
- qualquer um de
tools:webhooks:managetools:manage
Reenvia para este endpoint todas as entregas que terminaram com falha (failed, exhausted ou canceled) desde since (RFC 3339), até 1.000 por chamada. Use depois de corrigir um receptor que ficou fora do ar. Cada entrega reenviada é cobrada como nova.
curl -sS -X POST 'https://api.respawntech.dev/v1/tools/webhooks/endpoints/550e8400-e29b-41d4-a716-446655440000/recover?since=2026-09-24T00%3A00%3A00Z' \
-H "X-API-Token: $RESPAWN_API_TOKEN"const response = await fetch('https://api.respawntech.dev/v1/tools/webhooks/endpoints/550e8400-e29b-41d4-a716-446655440000/recover?since=2026-09-24T00%3A00%3A00Z', {
method: 'POST',
headers: {
'X-API-Token': process.env.RESPAWN_API_TOKEN,
},
});
if (!response.ok) throw new Error((await response.json()).error.code);
console.log(await response.json());import os
import requests
response = requests.post(
"https://api.respawntech.dev/v1/tools/webhooks/endpoints/550e8400-e29b-41d4-a716-446655440000/recover?since=2026-09-24T00%3A00%3A00Z",
headers={
"X-API-Token": os.environ["RESPAWN_API_TOKEN"],
},
timeout=30,
)
if not response.ok:
raise RuntimeError(response.json()["error"]["code"])
print(response.json())<?php
$ch = curl_init('https://api.respawntech.dev/v1/tools/webhooks/endpoints/550e8400-e29b-41d4-a716-446655440000/recover?since=2026-09-24T00%3A00%3A00Z');
curl_setopt_array($ch, [
CURLOPT_CUSTOMREQUEST => 'POST',
CURLOPT_HTTPHEADER => [
'X-API-Token: ' . getenv('RESPAWN_API_TOKEN'),
],
CURLOPT_RETURNTRANSFER => true,
CURLOPT_TIMEOUT => 30,
]);
$body = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_RESPONSE_CODE);
if ($status >= 400) {
throw new RuntimeException(json_decode($body, true)['error']['code']);
}
print_r(json_decode($body, true));package main
import (
"fmt"
"io"
"net/http"
"os"
"time"
)
func main() {
req, err := http.NewRequest("POST", "https://api.respawntech.dev/v1/tools/webhooks/endpoints/550e8400-e29b-41d4-a716-446655440000/recover?since=2026-09-24T00%3A00%3A00Z", nil)
if err != nil {
panic(err)
}
req.Header.Set("X-API-Token", os.Getenv("RESPAWN_API_TOKEN"))
client := &http.Client{Timeout: 30 * time.Second}
res, err := client.Do(req)
if err != nil {
panic(err)
}
defer res.Body.Close()
data, err := io.ReadAll(res.Body)
if err != nil {
panic(err)
}
if res.StatusCode >= 400 {
panic(string(data))
}
fmt.Println(string(data))
}Resposta 202
{
"success": true,
"trace_id": "550e8400-e29b-41d4-a716-446655440000",
"data": {
"message_id": "c1a7e5d3-9b2f-4a6e-8d4c-0f3b7e1a5c92",
"duplicate": false,
"deliveries": 12,
"free_deliveries": 12,
"charged_units": 0
}
}Slack, Discord e Teams
Com format em slack, discord ou teams, cadastre como url o webhook de entrada do canal (Incoming Webhook do Slack ou do Teams, webhook do Discord). A plataforma transforma cada entrega numa mensagem de texto na hora de enviar:
- O corpo vira
{"text": "…"}(Slack e Teams) ou{"content": "…"}(Discord), comContent-Type: application/json. - O texto traz o tipo do evento em negrito (e o nome da fonte, nas mensagens recebidas de um provedor), seguido do corpo num bloco de código: JSON formatado ou o texto bruto.
- Textos longos são cortados com
…para caber no limite da plataforma: 3.000 caracteres no Slack, 2.000 no Discord e 20.000 no Teams. - Os headers de assinatura continuam sendo enviados, calculados sobre esse corpo; os headers originais de uma fonte com
forward_original_headersnão são repassados.
A mensagem guardada no log não muda: o log, o reenvio e a recuperação funcionam igual aos endpoints raw.
Mensagens
Enviar mensagem
/v1/tools/webhooks/messages- Custo
- Por entrega (mensagem × endpoint) além da franquia mensal
- Dry-run
- Suportado (
X-Dry-Run: true) - Autenticação
- qualquer um de
tools:webhooks:sendtools:webhooks:managetools:manage
event_type(obrigatório):^[a-z0-9][a-z0-9._-]{0,127}$, ex.:order.paid. Vai para os endpoints do projeto que assinam esse tipo (ou*).payload(obrigatório): qualquer JSON, até 256 KB. Se o tipo de evento tiver JSON Schema no projeto, o payload precisa segui-lo (webhook.payload_invalid, sem cobrança).project_id(opcional): projeto da mensagem (padrão: o projeto padrão).endpoint_ids(opcional): restringe o envio a estes endpoints, entre os do projeto que assinam o evento.idempotency_key(ou o headerX-Idempotency-Key): repetir a chave devolve a primeira mensagem comduplicate: truee não cobra nada. Use para reenviar com segurança depois de um timeout.- Com
X-Dry-Run: true, nada é enviado nem cobrado: a resposta mostradeliveries,free_deliveriesecharged_units.
O receptor recebe o envelope { "event_id", "type", "timestamp", "payload" } descrito em Webhooks; event_id é o message_id e webhook-id é msg_<message_id>.
curl -sS -X POST 'https://api.respawntech.dev/v1/tools/webhooks/messages' \
-H "X-API-Token: $RESPAWN_API_TOKEN" \
-H 'X-Dry-Run: true' \
-H 'Content-Type: application/json' \
-d '{
"event_type": "order.paid",
"payload": { "order_id": "ped_1042", "amount": 199.9 },
"idempotency_key": "order-paid-ped_1042"
}'const response = await fetch('https://api.respawntech.dev/v1/tools/webhooks/messages', {
method: 'POST',
headers: {
'X-API-Token': process.env.RESPAWN_API_TOKEN,
'X-Dry-Run': 'true',
'Content-Type': 'application/json',
},
body: JSON.stringify({
"event_type": "order.paid",
"payload": { "order_id": "ped_1042", "amount": 199.9 },
"idempotency_key": "order-paid-ped_1042"
}),
});
if (!response.ok) throw new Error((await response.json()).error.code);
console.log(await response.json());import os
import requests
response = requests.post(
"https://api.respawntech.dev/v1/tools/webhooks/messages",
headers={
"X-API-Token": os.environ["RESPAWN_API_TOKEN"],
"X-Dry-Run": "true",
},
json={
"event_type": "order.paid",
"payload": { "order_id": "ped_1042", "amount": 199.9 },
"idempotency_key": "order-paid-ped_1042"
},
timeout=30,
)
if not response.ok:
raise RuntimeError(response.json()["error"]["code"])
print(response.json())<?php
$ch = curl_init('https://api.respawntech.dev/v1/tools/webhooks/messages');
curl_setopt_array($ch, [
CURLOPT_CUSTOMREQUEST => 'POST',
CURLOPT_HTTPHEADER => [
'X-API-Token: ' . getenv('RESPAWN_API_TOKEN'),
'X-Dry-Run: true',
'Content-Type: application/json',
],
CURLOPT_POSTFIELDS => <<<'JSON'
{
"event_type": "order.paid",
"payload": { "order_id": "ped_1042", "amount": 199.9 },
"idempotency_key": "order-paid-ped_1042"
}
JSON,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_TIMEOUT => 30,
]);
$body = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_RESPONSE_CODE);
if ($status >= 400) {
throw new RuntimeException(json_decode($body, true)['error']['code']);
}
print_r(json_decode($body, true));package main
import (
"fmt"
"io"
"net/http"
"os"
"strings"
"time"
)
func main() {
req, err := http.NewRequest("POST", "https://api.respawntech.dev/v1/tools/webhooks/messages", strings.NewReader(`{
"event_type": "order.paid",
"payload": { "order_id": "ped_1042", "amount": 199.9 },
"idempotency_key": "order-paid-ped_1042"
}`))
if err != nil {
panic(err)
}
req.Header.Set("X-API-Token", os.Getenv("RESPAWN_API_TOKEN"))
req.Header.Set("X-Dry-Run", "true")
req.Header.Set("Content-Type", "application/json")
client := &http.Client{Timeout: 30 * time.Second}
res, err := client.Do(req)
if err != nil {
panic(err)
}
defer res.Body.Close()
data, err := io.ReadAll(res.Body)
if err != nil {
panic(err)
}
if res.StatusCode >= 400 {
panic(string(data))
}
fmt.Println(string(data))
}Resposta 202 — 200 em dry-run (dry_run: true, nada é enviado) e quando a chave de idempotência já foi usada (duplicate: true, sem cobrança)
{
"success": true,
"trace_id": "550e8400-e29b-41d4-a716-446655440000",
"data": {
"message_id": "c1a7e5d3-9b2f-4a6e-8d4c-0f3b7e1a5c92",
"duplicate": false,
"deliveries": 2,
"free_deliveries": 0,
"charged_units": 40
}
}Enviar mensagens em lote
/v1/tools/webhooks/messages/batch- Custo
- Por entrega, cada mensagem cobrada separadamente
- Dry-run
- Suportado (
X-Dry-Run: true) - Autenticação
- qualquer um de
tools:webhooks:sendtools:webhooks:managetools:manage
Até 100 mensagens, cada uma com os mesmos campos do envio único. O project_id fica no nível de cima do body e vale para todas as mensagens do lote. Cada mensagem é validada, deduplicada e cobrada sozinha: uma falha aparece em error no item dela e não impede as outras. Aceita X-Dry-Run: true.
curl -sS -X POST 'https://api.respawntech.dev/v1/tools/webhooks/messages/batch' \
-H "X-API-Token: $RESPAWN_API_TOKEN" \
-H 'Content-Type: application/json' \
-d '{
"messages": [
{ "event_type": "order.paid", "payload": { "order_id": "ped_1042", "amount": 199.9 }, "idempotency_key": "order-paid-ped_1042" },
{ "event_type": "order.paid", "payload": { "order_id": "ped_1043" }, "idempotency_key": "order-paid-ped_1043" }
]
}'const response = await fetch('https://api.respawntech.dev/v1/tools/webhooks/messages/batch', {
method: 'POST',
headers: {
'X-API-Token': process.env.RESPAWN_API_TOKEN,
'Content-Type': 'application/json',
},
body: JSON.stringify({
"messages": [
{ "event_type": "order.paid", "payload": { "order_id": "ped_1042", "amount": 199.9 }, "idempotency_key": "order-paid-ped_1042" },
{ "event_type": "order.paid", "payload": { "order_id": "ped_1043" }, "idempotency_key": "order-paid-ped_1043" }
]
}),
});
if (!response.ok) throw new Error((await response.json()).error.code);
console.log(await response.json());import os
import requests
response = requests.post(
"https://api.respawntech.dev/v1/tools/webhooks/messages/batch",
headers={
"X-API-Token": os.environ["RESPAWN_API_TOKEN"],
},
json={
"messages": [
{ "event_type": "order.paid", "payload": { "order_id": "ped_1042", "amount": 199.9 }, "idempotency_key": "order-paid-ped_1042" },
{ "event_type": "order.paid", "payload": { "order_id": "ped_1043" }, "idempotency_key": "order-paid-ped_1043" }
]
},
timeout=30,
)
if not response.ok:
raise RuntimeError(response.json()["error"]["code"])
print(response.json())<?php
$ch = curl_init('https://api.respawntech.dev/v1/tools/webhooks/messages/batch');
curl_setopt_array($ch, [
CURLOPT_CUSTOMREQUEST => 'POST',
CURLOPT_HTTPHEADER => [
'X-API-Token: ' . getenv('RESPAWN_API_TOKEN'),
'Content-Type: application/json',
],
CURLOPT_POSTFIELDS => <<<'JSON'
{
"messages": [
{ "event_type": "order.paid", "payload": { "order_id": "ped_1042", "amount": 199.9 }, "idempotency_key": "order-paid-ped_1042" },
{ "event_type": "order.paid", "payload": { "order_id": "ped_1043" }, "idempotency_key": "order-paid-ped_1043" }
]
}
JSON,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_TIMEOUT => 30,
]);
$body = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_RESPONSE_CODE);
if ($status >= 400) {
throw new RuntimeException(json_decode($body, true)['error']['code']);
}
print_r(json_decode($body, true));package main
import (
"fmt"
"io"
"net/http"
"os"
"strings"
"time"
)
func main() {
req, err := http.NewRequest("POST", "https://api.respawntech.dev/v1/tools/webhooks/messages/batch", strings.NewReader(`{
"messages": [
{ "event_type": "order.paid", "payload": { "order_id": "ped_1042", "amount": 199.9 }, "idempotency_key": "order-paid-ped_1042" },
{ "event_type": "order.paid", "payload": { "order_id": "ped_1043" }, "idempotency_key": "order-paid-ped_1043" }
]
}`))
if err != nil {
panic(err)
}
req.Header.Set("X-API-Token", os.Getenv("RESPAWN_API_TOKEN"))
req.Header.Set("Content-Type", "application/json")
client := &http.Client{Timeout: 30 * time.Second}
res, err := client.Do(req)
if err != nil {
panic(err)
}
defer res.Body.Close()
data, err := io.ReadAll(res.Body)
if err != nil {
panic(err)
}
if res.StatusCode >= 400 {
panic(string(data))
}
fmt.Println(string(data))
}Resposta 200 — um item por mensagem, na ordem enviada; um item com erro traz error e não impede os demais
{
"success": true,
"trace_id": "550e8400-e29b-41d4-a716-446655440000",
"data": {
"results": [
{
"message_id": "c1a7e5d3-9b2f-4a6e-8d4c-0f3b7e1a5c92",
"duplicate": false,
"deliveries": 2,
"free_deliveries": 0,
"charged_units": 40
},
{
"message_id": "00000000-0000-0000-0000-000000000000",
"duplicate": false,
"deliveries": 0,
"free_deliveries": 0,
"charged_units": 0,
"error": {
"code": "webhook.payload_invalid",
"message": "payload does not match the event type schema: missing property \"amount\""
}
}
]
}
}Listar mensagens
/v1/tools/webhooks/messages- Custo
- Franquia diária, depois 0,002 crédito/leitura via API
- Dry-run
- Suportado (
X-Dry-Run: true) - Autenticação
- qualquer um de
tools:webhooks:readtools:webhooks:managetools:readtools:manage
Mais recentes primeiro, com as entregas de cada mensagem (sem o corpo). Paginação: passe o next_before recebido em before. limit de 1 a 100 (padrão 20). As mensagens ficam no log por 30 dias.
project_id: só as mensagens desse projeto. Sem ele, o log traz todos os projetos, além das mensagens sem projeto (project_id: null: callbacks de jobs e mensagens de projetos excluídos).event_typeeendpoint_id: filtros exatos.failed=true: só mensagens com ao menos uma entregafailed,exhaustedoublocked_no_credits.q: busca, de 1 a 200 caracteres, combinável com os outros filtros. Um UUID encontra a mensagem com esse id. Outro texto encontra a chave de idempotência ou o tipo de evento iguais ao texto, ou o texto dentro do corpo (sem diferenciar maiúsculas, exceto letras acentuadas); a busca no corpo só olha as mensagens dos últimos 30 dias.
curl -sS -X GET 'https://api.respawntech.dev/v1/tools/webhooks/messages?event_type=order.paid&failed=true&limit=20' \
-H "X-API-Token: $RESPAWN_API_TOKEN"const response = await fetch('https://api.respawntech.dev/v1/tools/webhooks/messages?event_type=order.paid&failed=true&limit=20', {
method: 'GET',
headers: {
'X-API-Token': process.env.RESPAWN_API_TOKEN,
},
});
if (!response.ok) throw new Error((await response.json()).error.code);
console.log(await response.json());import os
import requests
response = requests.get(
"https://api.respawntech.dev/v1/tools/webhooks/messages?event_type=order.paid&failed=true&limit=20",
headers={
"X-API-Token": os.environ["RESPAWN_API_TOKEN"],
},
timeout=30,
)
if not response.ok:
raise RuntimeError(response.json()["error"]["code"])
print(response.json())<?php
$ch = curl_init('https://api.respawntech.dev/v1/tools/webhooks/messages?event_type=order.paid&failed=true&limit=20');
curl_setopt_array($ch, [
CURLOPT_CUSTOMREQUEST => 'GET',
CURLOPT_HTTPHEADER => [
'X-API-Token: ' . getenv('RESPAWN_API_TOKEN'),
],
CURLOPT_RETURNTRANSFER => true,
CURLOPT_TIMEOUT => 30,
]);
$body = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_RESPONSE_CODE);
if ($status >= 400) {
throw new RuntimeException(json_decode($body, true)['error']['code']);
}
print_r(json_decode($body, true));package main
import (
"fmt"
"io"
"net/http"
"os"
"time"
)
func main() {
req, err := http.NewRequest("GET", "https://api.respawntech.dev/v1/tools/webhooks/messages?event_type=order.paid&failed=true&limit=20", nil)
if err != nil {
panic(err)
}
req.Header.Set("X-API-Token", os.Getenv("RESPAWN_API_TOKEN"))
client := &http.Client{Timeout: 30 * time.Second}
res, err := client.Do(req)
if err != nil {
panic(err)
}
defer res.Body.Close()
data, err := io.ReadAll(res.Body)
if err != nil {
panic(err)
}
if res.StatusCode >= 400 {
panic(string(data))
}
fmt.Println(string(data))
}Resposta 200
{
"success": true,
"trace_id": "550e8400-e29b-41d4-a716-446655440000",
"data": {
"messages": [
{
"id": "c1a7e5d3-9b2f-4a6e-8d4c-0f3b7e1a5c92",
"project_id": "3e7c1a9f-5b2d-4c8e-a6f0-9d1b4e7a2c53",
"event_type": "order.paid",
"origin": "api",
"created_at": "2026-09-25T12:05:00Z",
"deliveries": [
{
"id": "e4b9c2a7-1d6f-4e3b-a8c5-2f7d0b9e6a13",
"endpoint_id": "8d2f4a61-3c7b-4e9a-b1d5-6f0e2a9c7b34",
"target_url": "https://api.example.com/hooks/respawn",
"status": "succeeded",
"attempt_count": 2,
"last_status_code": 200,
"charged": true,
"created_at": "2026-09-25T12:05:00Z"
}
]
}
],
"next_before": "2026-09-25T12:05:00.123456Z"
}
}Consultar mensagem
/v1/tools/webhooks/messages/{id}- Custo
- Franquia diária, depois 0,002 crédito/leitura via API
- Dry-run
- Suportado (
X-Dry-Run: true) - Autenticação
- qualquer um de
tools:webhooks:readtools:webhooks:managetools:readtools:manage
Traz o corpo enviado e, por entrega, cada tentativa: http_status, latency_ms, o começo da resposta (response_body) ou o erro de rede (error). next_attempt_at aparece enquanto a entrega está pendente.
status da entrega | Significado |
|---|---|
pending | Aguardando a próxima tentativa (next_attempt_at). |
sending | Tentativa em andamento. |
succeeded | O endpoint respondeu 2xx. |
exhausted | Todas as tentativas falharam. Pode ser reenviada. |
failed | Parou de vez: endpoint respondeu 410 Gone, foi desativado ou a URL ficou inválida. Pode ser reenviada. |
canceled | Cancelada, por exemplo porque o endpoint foi excluído com a entrega pendente. |
blocked_no_credits | Mensagem recebida de um provedor sem saldo para a entrega (veja Sem créditos). |
curl -sS -X GET 'https://api.respawntech.dev/v1/tools/webhooks/messages/550e8400-e29b-41d4-a716-446655440000' \
-H "X-API-Token: $RESPAWN_API_TOKEN"const response = await fetch('https://api.respawntech.dev/v1/tools/webhooks/messages/550e8400-e29b-41d4-a716-446655440000', {
method: 'GET',
headers: {
'X-API-Token': process.env.RESPAWN_API_TOKEN,
},
});
if (!response.ok) throw new Error((await response.json()).error.code);
console.log(await response.json());import os
import requests
response = requests.get(
"https://api.respawntech.dev/v1/tools/webhooks/messages/550e8400-e29b-41d4-a716-446655440000",
headers={
"X-API-Token": os.environ["RESPAWN_API_TOKEN"],
},
timeout=30,
)
if not response.ok:
raise RuntimeError(response.json()["error"]["code"])
print(response.json())<?php
$ch = curl_init('https://api.respawntech.dev/v1/tools/webhooks/messages/550e8400-e29b-41d4-a716-446655440000');
curl_setopt_array($ch, [
CURLOPT_CUSTOMREQUEST => 'GET',
CURLOPT_HTTPHEADER => [
'X-API-Token: ' . getenv('RESPAWN_API_TOKEN'),
],
CURLOPT_RETURNTRANSFER => true,
CURLOPT_TIMEOUT => 30,
]);
$body = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_RESPONSE_CODE);
if ($status >= 400) {
throw new RuntimeException(json_decode($body, true)['error']['code']);
}
print_r(json_decode($body, true));package main
import (
"fmt"
"io"
"net/http"
"os"
"time"
)
func main() {
req, err := http.NewRequest("GET", "https://api.respawntech.dev/v1/tools/webhooks/messages/550e8400-e29b-41d4-a716-446655440000", nil)
if err != nil {
panic(err)
}
req.Header.Set("X-API-Token", os.Getenv("RESPAWN_API_TOKEN"))
client := &http.Client{Timeout: 30 * time.Second}
res, err := client.Do(req)
if err != nil {
panic(err)
}
defer res.Body.Close()
data, err := io.ReadAll(res.Body)
if err != nil {
panic(err)
}
if res.StatusCode >= 400 {
panic(string(data))
}
fmt.Println(string(data))
}Resposta 200
{
"success": true,
"trace_id": "550e8400-e29b-41d4-a716-446655440000",
"data": {
"id": "c1a7e5d3-9b2f-4a6e-8d4c-0f3b7e1a5c92",
"project_id": "3e7c1a9f-5b2d-4c8e-a6f0-9d1b4e7a2c53",
"event_type": "order.paid",
"origin": "api",
"body": {
"event_id": "c1a7e5d3-9b2f-4a6e-8d4c-0f3b7e1a5c92",
"type": "order.paid",
"timestamp": 1790338000,
"payload": {
"order_id": "ped_1042",
"amount": 199.9
}
},
"created_at": "2026-09-25T12:05:00Z",
"deliveries": [
{
"id": "e4b9c2a7-1d6f-4e3b-a8c5-2f7d0b9e6a13",
"endpoint_id": "8d2f4a61-3c7b-4e9a-b1d5-6f0e2a9c7b34",
"target_url": "https://api.example.com/hooks/respawn",
"status": "succeeded",
"attempt_count": 2,
"last_status_code": 200,
"charged": true,
"created_at": "2026-09-25T12:05:00Z",
"attempts": [
{
"attempt": 1,
"http_status": 503,
"latency_ms": 412,
"response_body": "Service Unavailable",
"created_at": "2026-09-25T12:05:00Z"
},
{
"attempt": 2,
"http_status": 200,
"latency_ms": 88,
"created_at": "2026-09-25T12:05:06Z"
}
]
}
]
}
}Reenviar mensagem
/v1/tools/webhooks/messages/{id}/retry- Custo
- Cada entrega reenviada é cobrada como uma nova (conta na franquia mensal)
- Dry-run
- Não
- Autenticação
- qualquer um de
tools:webhooks:managetools:manage
Sem body, envia a mensagem de novo para os endpoints cuja entrega terminou com falha (failed, exhausted ou canceled) e que estão ativos.
Com endpoint_id, envia só para esse endpoint, qualquer que tenha sido o resultado anterior (mesmo que a entrega tenha dado certo). Ele precisa ser um endpoint ativo do projeto da mensagem; caso contrário, e para mensagens sem projeto, a resposta é 400. As entregas novas são cobradas como um envio.
curl -sS -X POST 'https://api.respawntech.dev/v1/tools/webhooks/messages/550e8400-e29b-41d4-a716-446655440000/retry' \
-H "X-API-Token: $RESPAWN_API_TOKEN" \
-H 'Content-Type: application/json' \
-d '{ "endpoint_id": "8d2f4a61-3c7b-4e9a-b1d5-6f0e2a9c7b34" }'const response = await fetch('https://api.respawntech.dev/v1/tools/webhooks/messages/550e8400-e29b-41d4-a716-446655440000/retry', {
method: 'POST',
headers: {
'X-API-Token': process.env.RESPAWN_API_TOKEN,
'Content-Type': 'application/json',
},
body: JSON.stringify({ "endpoint_id": "8d2f4a61-3c7b-4e9a-b1d5-6f0e2a9c7b34" }),
});
if (!response.ok) throw new Error((await response.json()).error.code);
console.log(await response.json());import os
import requests
response = requests.post(
"https://api.respawntech.dev/v1/tools/webhooks/messages/550e8400-e29b-41d4-a716-446655440000/retry",
headers={
"X-API-Token": os.environ["RESPAWN_API_TOKEN"],
},
json={ "endpoint_id": "8d2f4a61-3c7b-4e9a-b1d5-6f0e2a9c7b34" },
timeout=30,
)
if not response.ok:
raise RuntimeError(response.json()["error"]["code"])
print(response.json())<?php
$ch = curl_init('https://api.respawntech.dev/v1/tools/webhooks/messages/550e8400-e29b-41d4-a716-446655440000/retry');
curl_setopt_array($ch, [
CURLOPT_CUSTOMREQUEST => 'POST',
CURLOPT_HTTPHEADER => [
'X-API-Token: ' . getenv('RESPAWN_API_TOKEN'),
'Content-Type: application/json',
],
CURLOPT_POSTFIELDS => <<<'JSON'
{ "endpoint_id": "8d2f4a61-3c7b-4e9a-b1d5-6f0e2a9c7b34" }
JSON,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_TIMEOUT => 30,
]);
$body = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_RESPONSE_CODE);
if ($status >= 400) {
throw new RuntimeException(json_decode($body, true)['error']['code']);
}
print_r(json_decode($body, true));package main
import (
"fmt"
"io"
"net/http"
"os"
"strings"
"time"
)
func main() {
req, err := http.NewRequest("POST", "https://api.respawntech.dev/v1/tools/webhooks/messages/550e8400-e29b-41d4-a716-446655440000/retry", strings.NewReader(`{ "endpoint_id": "8d2f4a61-3c7b-4e9a-b1d5-6f0e2a9c7b34" }`))
if err != nil {
panic(err)
}
req.Header.Set("X-API-Token", os.Getenv("RESPAWN_API_TOKEN"))
req.Header.Set("Content-Type", "application/json")
client := &http.Client{Timeout: 30 * time.Second}
res, err := client.Do(req)
if err != nil {
panic(err)
}
defer res.Body.Close()
data, err := io.ReadAll(res.Body)
if err != nil {
panic(err)
}
if res.StatusCode >= 400 {
panic(string(data))
}
fmt.Println(string(data))
}Resposta 202
{
"success": true,
"trace_id": "550e8400-e29b-41d4-a716-446655440000",
"data": {
"message_id": "c1a7e5d3-9b2f-4a6e-8d4c-0f3b7e1a5c92",
"duplicate": false,
"deliveries": 1,
"free_deliveries": 0,
"charged_units": 20
}
}Tipos de evento
Declarar tipos de evento é opcional: sem declaração, qualquer event_type válido é aceito. Declare para documentar os eventos da sua integração e, com schema, validar o payload antes de cobrar e enviar. Cada tipo pertence a um projeto e só vale para as mensagens desse projeto.
Declarar tipo de evento
/v1/tools/webhooks/event-types- Custo
- Grátis
- Dry-run
- Não
- Autenticação
- qualquer um de
tools:webhooks:managetools:manage
schema é um JSON Schema aplicado ao payload de cada envio desse tipo; um payload fora do schema responde webhook.payload_invalid e não é cobrado. project_id é opcional (padrão: o projeto padrão); o nome é único dentro do projeto. Até 100 tipos por conta, somando todos os projetos.
curl -sS -X POST 'https://api.respawntech.dev/v1/tools/webhooks/event-types' \
-H "X-API-Token: $RESPAWN_API_TOKEN" \
-H 'Content-Type: application/json' \
-d '{
"name": "order.paid",
"description": "Pedido pago",
"schema": {
"type": "object",
"required": ["order_id", "amount"],
"properties": {
"order_id": { "type": "string" },
"amount": { "type": "number" }
}
}
}'const response = await fetch('https://api.respawntech.dev/v1/tools/webhooks/event-types', {
method: 'POST',
headers: {
'X-API-Token': process.env.RESPAWN_API_TOKEN,
'Content-Type': 'application/json',
},
body: JSON.stringify({
"name": "order.paid",
"description": "Pedido pago",
"schema": {
"type": "object",
"required": ["order_id", "amount"],
"properties": {
"order_id": { "type": "string" },
"amount": { "type": "number" }
}
}
}),
});
if (!response.ok) throw new Error((await response.json()).error.code);
console.log(await response.json());import os
import requests
response = requests.post(
"https://api.respawntech.dev/v1/tools/webhooks/event-types",
headers={
"X-API-Token": os.environ["RESPAWN_API_TOKEN"],
},
json={
"name": "order.paid",
"description": "Pedido pago",
"schema": {
"type": "object",
"required": ["order_id", "amount"],
"properties": {
"order_id": { "type": "string" },
"amount": { "type": "number" }
}
}
},
timeout=30,
)
if not response.ok:
raise RuntimeError(response.json()["error"]["code"])
print(response.json())<?php
$ch = curl_init('https://api.respawntech.dev/v1/tools/webhooks/event-types');
curl_setopt_array($ch, [
CURLOPT_CUSTOMREQUEST => 'POST',
CURLOPT_HTTPHEADER => [
'X-API-Token: ' . getenv('RESPAWN_API_TOKEN'),
'Content-Type: application/json',
],
CURLOPT_POSTFIELDS => <<<'JSON'
{
"name": "order.paid",
"description": "Pedido pago",
"schema": {
"type": "object",
"required": ["order_id", "amount"],
"properties": {
"order_id": { "type": "string" },
"amount": { "type": "number" }
}
}
}
JSON,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_TIMEOUT => 30,
]);
$body = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_RESPONSE_CODE);
if ($status >= 400) {
throw new RuntimeException(json_decode($body, true)['error']['code']);
}
print_r(json_decode($body, true));package main
import (
"fmt"
"io"
"net/http"
"os"
"strings"
"time"
)
func main() {
req, err := http.NewRequest("POST", "https://api.respawntech.dev/v1/tools/webhooks/event-types", strings.NewReader(`{
"name": "order.paid",
"description": "Pedido pago",
"schema": {
"type": "object",
"required": ["order_id", "amount"],
"properties": {
"order_id": { "type": "string" },
"amount": { "type": "number" }
}
}
}`))
if err != nil {
panic(err)
}
req.Header.Set("X-API-Token", os.Getenv("RESPAWN_API_TOKEN"))
req.Header.Set("Content-Type", "application/json")
client := &http.Client{Timeout: 30 * time.Second}
res, err := client.Do(req)
if err != nil {
panic(err)
}
defer res.Body.Close()
data, err := io.ReadAll(res.Body)
if err != nil {
panic(err)
}
if res.StatusCode >= 400 {
panic(string(data))
}
fmt.Println(string(data))
}Resposta 201
{
"success": true,
"trace_id": "550e8400-e29b-41d4-a716-446655440000",
"data": {
"id": "5b3e8f1a-7c2d-4a9e-b6f0-1d4c8e2a7b59",
"project_id": "3e7c1a9f-5b2d-4c8e-a6f0-9d1b4e7a2c53",
"name": "order.paid",
"description": "Pedido pago",
"schema": {
"type": "object",
"required": [
"order_id",
"amount"
],
"properties": {
"order_id": {
"type": "string"
},
"amount": {
"type": "number"
}
}
},
"created_at": "2026-09-25T11:50:00Z"
}
}Listar tipos de evento
/v1/tools/webhooks/event-types- Custo
- Grátis
- Dry-run
- Não
- Autenticação
- qualquer um de
tools:webhooks:readtools:webhooks:managetools:readtools:manage
Filtro opcional por project_id.
curl -sS -X GET 'https://api.respawntech.dev/v1/tools/webhooks/event-types' \
-H "X-API-Token: $RESPAWN_API_TOKEN"const response = await fetch('https://api.respawntech.dev/v1/tools/webhooks/event-types', {
method: 'GET',
headers: {
'X-API-Token': process.env.RESPAWN_API_TOKEN,
},
});
if (!response.ok) throw new Error((await response.json()).error.code);
console.log(await response.json());import os
import requests
response = requests.get(
"https://api.respawntech.dev/v1/tools/webhooks/event-types",
headers={
"X-API-Token": os.environ["RESPAWN_API_TOKEN"],
},
timeout=30,
)
if not response.ok:
raise RuntimeError(response.json()["error"]["code"])
print(response.json())<?php
$ch = curl_init('https://api.respawntech.dev/v1/tools/webhooks/event-types');
curl_setopt_array($ch, [
CURLOPT_CUSTOMREQUEST => 'GET',
CURLOPT_HTTPHEADER => [
'X-API-Token: ' . getenv('RESPAWN_API_TOKEN'),
],
CURLOPT_RETURNTRANSFER => true,
CURLOPT_TIMEOUT => 30,
]);
$body = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_RESPONSE_CODE);
if ($status >= 400) {
throw new RuntimeException(json_decode($body, true)['error']['code']);
}
print_r(json_decode($body, true));package main
import (
"fmt"
"io"
"net/http"
"os"
"time"
)
func main() {
req, err := http.NewRequest("GET", "https://api.respawntech.dev/v1/tools/webhooks/event-types", nil)
if err != nil {
panic(err)
}
req.Header.Set("X-API-Token", os.Getenv("RESPAWN_API_TOKEN"))
client := &http.Client{Timeout: 30 * time.Second}
res, err := client.Do(req)
if err != nil {
panic(err)
}
defer res.Body.Close()
data, err := io.ReadAll(res.Body)
if err != nil {
panic(err)
}
if res.StatusCode >= 400 {
panic(string(data))
}
fmt.Println(string(data))
}Resposta 200
{
"success": true,
"trace_id": "550e8400-e29b-41d4-a716-446655440000",
"data": {
"event_types": [
{
"id": "5b3e8f1a-7c2d-4a9e-b6f0-1d4c8e2a7b59",
"project_id": "3e7c1a9f-5b2d-4c8e-a6f0-9d1b4e7a2c53",
"name": "order.paid",
"description": "Pedido pago",
"schema": {
"type": "object",
"required": [
"order_id",
"amount"
],
"properties": {
"order_id": {
"type": "string"
},
"amount": {
"type": "number"
}
}
},
"created_at": "2026-09-25T11:50:00Z"
}
]
}
}Excluir tipo de evento
/v1/tools/webhooks/event-types/{id}- Custo
- Grátis
- Dry-run
- Não
- Autenticação
- qualquer um de
tools:webhooks:managetools:manage
Os envios desse tipo continuam aceitos, sem validação de schema.
curl -sS -X DELETE 'https://api.respawntech.dev/v1/tools/webhooks/event-types/550e8400-e29b-41d4-a716-446655440000' \
-H "X-API-Token: $RESPAWN_API_TOKEN"const response = await fetch('https://api.respawntech.dev/v1/tools/webhooks/event-types/550e8400-e29b-41d4-a716-446655440000', {
method: 'DELETE',
headers: {
'X-API-Token': process.env.RESPAWN_API_TOKEN,
},
});
if (!response.ok) throw new Error((await response.json()).error.code);
console.log(await response.json());import os
import requests
response = requests.delete(
"https://api.respawntech.dev/v1/tools/webhooks/event-types/550e8400-e29b-41d4-a716-446655440000",
headers={
"X-API-Token": os.environ["RESPAWN_API_TOKEN"],
},
timeout=30,
)
if not response.ok:
raise RuntimeError(response.json()["error"]["code"])
print(response.json())<?php
$ch = curl_init('https://api.respawntech.dev/v1/tools/webhooks/event-types/550e8400-e29b-41d4-a716-446655440000');
curl_setopt_array($ch, [
CURLOPT_CUSTOMREQUEST => 'DELETE',
CURLOPT_HTTPHEADER => [
'X-API-Token: ' . getenv('RESPAWN_API_TOKEN'),
],
CURLOPT_RETURNTRANSFER => true,
CURLOPT_TIMEOUT => 30,
]);
$body = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_RESPONSE_CODE);
if ($status >= 400) {
throw new RuntimeException(json_decode($body, true)['error']['code']);
}
print_r(json_decode($body, true));package main
import (
"fmt"
"io"
"net/http"
"os"
"time"
)
func main() {
req, err := http.NewRequest("DELETE", "https://api.respawntech.dev/v1/tools/webhooks/event-types/550e8400-e29b-41d4-a716-446655440000", nil)
if err != nil {
panic(err)
}
req.Header.Set("X-API-Token", os.Getenv("RESPAWN_API_TOKEN"))
client := &http.Client{Timeout: 30 * time.Second}
res, err := client.Do(req)
if err != nil {
panic(err)
}
defer res.Body.Close()
data, err := io.ReadAll(res.Body)
if err != nil {
panic(err)
}
if res.StatusCode >= 400 {
panic(string(data))
}
fmt.Println(string(data))
}Resposta 200
{
"success": true,
"trace_id": "550e8400-e29b-41d4-a716-446655440000",
"data": {
"deleted": true
}
}Receber webhooks de terceiros
Uma fonte é uma URL da plataforma (ingest_url) que você cadastra no provedor (Stripe, GitHub, Shopify…). Cada evento recebido tem a assinatura do provedor verificada, é gravado e redistribuído pelas rotas da fonte para os seus endpoints, com a mesma assinatura, retries e log das mensagens da API. Assim, um único webhook do provedor pode alimentar vários serviços, e o provedor nunca fica esperando pelo seu receptor.
provider | Como a requisição é verificada |
|---|---|
stripe | Header Stripe-Signature com o signing secret do Stripe (whsec_…), tolerância de 5 minutos. |
github | Header X-Hub-Signature-256 com o secret do webhook do GitHub. |
shopify | Header X-Shopify-Hmac-Sha256 com o secret do app Shopify. |
hmac_generic | Header X-Webhook-Signature: sha256=<hex> (HMAC-SHA256 do corpo). |
standard_webhooks | Headers webhook-id, webhook-timestamp e webhook-signature. |
basic_auth | Header Authorization: Basic …; o secret é usuario:senha. |
none | Sem verificação: qualquer um com a URL pode enviar. Evite em produção. |
hmac_generic, standard_webhooks e basic_auth servem para qualquer sistema que não esteja na lista: escolha o que o sistema de origem sabe enviar.
Criar fonte de ingestão
/v1/tools/webhooks/sources- Custo
- Grátis
- Dry-run
- Não
- Autenticação
- qualquer um de
tools:webhooks:managetools:manage
nameeprovider(obrigatórios);project_idé opcional (padrão: o projeto padrão).verification_secret: o secret que o provedor usa para assinar. Ele nunca volta nas respostas (has_verification_secret). Pode ficar para depois, porque vários provedores só mostram o secret depois que você cadastra aingest_url: enquanto ele não for definido em Definir secret do provedor, a fonte responde401a toda requisição (exceto comprovider: "none").event_type_path: caminho no JSON recebido de onde vem o tipo do evento (ex.:type,data.object.status). Sem ele, o tipo éingest.dedupe_key_path: caminho do identificador do evento (ex.:id). Um reenvio do provedor com a mesma chave responde200sem gerar novas entregas.forward_original_headers: repassa os headers do provedor (ex.:Stripe-Signature) junto com o corpo idêntico, além da nossa assinatura. Útil quando o seu receptor já valida a assinatura do provedor. Cookies,Authorizatione headers de proxy nunca são repassados.- Cadastre a
ingest_urlda resposta no provedor. Até 200 fontes por conta, somando todos os projetos.
curl -sS -X POST 'https://api.respawntech.dev/v1/tools/webhooks/sources' \
-H "X-API-Token: $RESPAWN_API_TOKEN" \
-H 'Content-Type: application/json' \
-d '{
"name": "Stripe produção",
"provider": "stripe",
"verification_secret": "whsec_...",
"event_type_path": "type",
"dedupe_key_path": "id"
}'const response = await fetch('https://api.respawntech.dev/v1/tools/webhooks/sources', {
method: 'POST',
headers: {
'X-API-Token': process.env.RESPAWN_API_TOKEN,
'Content-Type': 'application/json',
},
body: JSON.stringify({
"name": "Stripe produção",
"provider": "stripe",
"verification_secret": "whsec_...",
"event_type_path": "type",
"dedupe_key_path": "id"
}),
});
if (!response.ok) throw new Error((await response.json()).error.code);
console.log(await response.json());import os
import requests
response = requests.post(
"https://api.respawntech.dev/v1/tools/webhooks/sources",
headers={
"X-API-Token": os.environ["RESPAWN_API_TOKEN"],
},
json={
"name": "Stripe produção",
"provider": "stripe",
"verification_secret": "whsec_...",
"event_type_path": "type",
"dedupe_key_path": "id"
},
timeout=30,
)
if not response.ok:
raise RuntimeError(response.json()["error"]["code"])
print(response.json())<?php
$ch = curl_init('https://api.respawntech.dev/v1/tools/webhooks/sources');
curl_setopt_array($ch, [
CURLOPT_CUSTOMREQUEST => 'POST',
CURLOPT_HTTPHEADER => [
'X-API-Token: ' . getenv('RESPAWN_API_TOKEN'),
'Content-Type: application/json',
],
CURLOPT_POSTFIELDS => <<<'JSON'
{
"name": "Stripe produção",
"provider": "stripe",
"verification_secret": "whsec_...",
"event_type_path": "type",
"dedupe_key_path": "id"
}
JSON,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_TIMEOUT => 30,
]);
$body = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_RESPONSE_CODE);
if ($status >= 400) {
throw new RuntimeException(json_decode($body, true)['error']['code']);
}
print_r(json_decode($body, true));package main
import (
"fmt"
"io"
"net/http"
"os"
"strings"
"time"
)
func main() {
req, err := http.NewRequest("POST", "https://api.respawntech.dev/v1/tools/webhooks/sources", strings.NewReader(`{
"name": "Stripe produção",
"provider": "stripe",
"verification_secret": "whsec_...",
"event_type_path": "type",
"dedupe_key_path": "id"
}`))
if err != nil {
panic(err)
}
req.Header.Set("X-API-Token", os.Getenv("RESPAWN_API_TOKEN"))
req.Header.Set("Content-Type", "application/json")
client := &http.Client{Timeout: 30 * time.Second}
res, err := client.Do(req)
if err != nil {
panic(err)
}
defer res.Body.Close()
data, err := io.ReadAll(res.Body)
if err != nil {
panic(err)
}
if res.StatusCode >= 400 {
panic(string(data))
}
fmt.Println(string(data))
}Resposta 201
{
"success": true,
"trace_id": "550e8400-e29b-41d4-a716-446655440000",
"data": {
"id": "a6d1f8c3-2e9b-4c7a-9f5d-3b0e6a1c8d27",
"project_id": "3e7c1a9f-5b2d-4c8e-a6f0-9d1b4e7a2c53",
"name": "Stripe produção",
"provider": "stripe",
"ingest_url": "https://api.respawntech.dev/in/src_4f8a2c6e1b9d3f7a5c0e8b2d6f4a1c9e7b3d5f0a2c8e6b4d",
"has_verification_secret": true,
"event_type_path": "type",
"dedupe_key_path": "id",
"forward_original_headers": false,
"is_active": true,
"created_at": "2026-09-25T12:10:00Z",
"updated_at": "2026-09-25T12:10:00Z"
}
}Listar fontes
/v1/tools/webhooks/sources- Custo
- Grátis
- Dry-run
- Não
- Autenticação
- qualquer um de
tools:webhooks:readtools:webhooks:managetools:readtools:manage
Filtro opcional por project_id.
curl -sS -X GET 'https://api.respawntech.dev/v1/tools/webhooks/sources' \
-H "X-API-Token: $RESPAWN_API_TOKEN"const response = await fetch('https://api.respawntech.dev/v1/tools/webhooks/sources', {
method: 'GET',
headers: {
'X-API-Token': process.env.RESPAWN_API_TOKEN,
},
});
if (!response.ok) throw new Error((await response.json()).error.code);
console.log(await response.json());import os
import requests
response = requests.get(
"https://api.respawntech.dev/v1/tools/webhooks/sources",
headers={
"X-API-Token": os.environ["RESPAWN_API_TOKEN"],
},
timeout=30,
)
if not response.ok:
raise RuntimeError(response.json()["error"]["code"])
print(response.json())<?php
$ch = curl_init('https://api.respawntech.dev/v1/tools/webhooks/sources');
curl_setopt_array($ch, [
CURLOPT_CUSTOMREQUEST => 'GET',
CURLOPT_HTTPHEADER => [
'X-API-Token: ' . getenv('RESPAWN_API_TOKEN'),
],
CURLOPT_RETURNTRANSFER => true,
CURLOPT_TIMEOUT => 30,
]);
$body = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_RESPONSE_CODE);
if ($status >= 400) {
throw new RuntimeException(json_decode($body, true)['error']['code']);
}
print_r(json_decode($body, true));package main
import (
"fmt"
"io"
"net/http"
"os"
"time"
)
func main() {
req, err := http.NewRequest("GET", "https://api.respawntech.dev/v1/tools/webhooks/sources", nil)
if err != nil {
panic(err)
}
req.Header.Set("X-API-Token", os.Getenv("RESPAWN_API_TOKEN"))
client := &http.Client{Timeout: 30 * time.Second}
res, err := client.Do(req)
if err != nil {
panic(err)
}
defer res.Body.Close()
data, err := io.ReadAll(res.Body)
if err != nil {
panic(err)
}
if res.StatusCode >= 400 {
panic(string(data))
}
fmt.Println(string(data))
}Resposta 200
{
"success": true,
"trace_id": "550e8400-e29b-41d4-a716-446655440000",
"data": {
"sources": [
{
"id": "a6d1f8c3-2e9b-4c7a-9f5d-3b0e6a1c8d27",
"project_id": "3e7c1a9f-5b2d-4c8e-a6f0-9d1b4e7a2c53",
"name": "Stripe produção",
"provider": "stripe",
"ingest_url": "https://api.respawntech.dev/in/src_4f8a2c6e1b9d3f7a5c0e8b2d6f4a1c9e7b3d5f0a2c8e6b4d",
"has_verification_secret": true,
"event_type_path": "type",
"dedupe_key_path": "id",
"forward_original_headers": false,
"is_active": true,
"created_at": "2026-09-25T12:10:00Z",
"updated_at": "2026-09-25T12:10:00Z"
}
]
}
}Consultar fonte
/v1/tools/webhooks/sources/{id}- Custo
- Grátis
- Dry-run
- Não
- Autenticação
- qualquer um de
tools:webhooks:readtools:webhooks:managetools:readtools:manage
curl -sS -X GET 'https://api.respawntech.dev/v1/tools/webhooks/sources/550e8400-e29b-41d4-a716-446655440000' \
-H "X-API-Token: $RESPAWN_API_TOKEN"const response = await fetch('https://api.respawntech.dev/v1/tools/webhooks/sources/550e8400-e29b-41d4-a716-446655440000', {
method: 'GET',
headers: {
'X-API-Token': process.env.RESPAWN_API_TOKEN,
},
});
if (!response.ok) throw new Error((await response.json()).error.code);
console.log(await response.json());import os
import requests
response = requests.get(
"https://api.respawntech.dev/v1/tools/webhooks/sources/550e8400-e29b-41d4-a716-446655440000",
headers={
"X-API-Token": os.environ["RESPAWN_API_TOKEN"],
},
timeout=30,
)
if not response.ok:
raise RuntimeError(response.json()["error"]["code"])
print(response.json())<?php
$ch = curl_init('https://api.respawntech.dev/v1/tools/webhooks/sources/550e8400-e29b-41d4-a716-446655440000');
curl_setopt_array($ch, [
CURLOPT_CUSTOMREQUEST => 'GET',
CURLOPT_HTTPHEADER => [
'X-API-Token: ' . getenv('RESPAWN_API_TOKEN'),
],
CURLOPT_RETURNTRANSFER => true,
CURLOPT_TIMEOUT => 30,
]);
$body = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_RESPONSE_CODE);
if ($status >= 400) {
throw new RuntimeException(json_decode($body, true)['error']['code']);
}
print_r(json_decode($body, true));package main
import (
"fmt"
"io"
"net/http"
"os"
"time"
)
func main() {
req, err := http.NewRequest("GET", "https://api.respawntech.dev/v1/tools/webhooks/sources/550e8400-e29b-41d4-a716-446655440000", nil)
if err != nil {
panic(err)
}
req.Header.Set("X-API-Token", os.Getenv("RESPAWN_API_TOKEN"))
client := &http.Client{Timeout: 30 * time.Second}
res, err := client.Do(req)
if err != nil {
panic(err)
}
defer res.Body.Close()
data, err := io.ReadAll(res.Body)
if err != nil {
panic(err)
}
if res.StatusCode >= 400 {
panic(string(data))
}
fmt.Println(string(data))
}Resposta 200
{
"success": true,
"trace_id": "550e8400-e29b-41d4-a716-446655440000",
"data": {
"id": "a6d1f8c3-2e9b-4c7a-9f5d-3b0e6a1c8d27",
"project_id": "3e7c1a9f-5b2d-4c8e-a6f0-9d1b4e7a2c53",
"name": "Stripe produção",
"provider": "stripe",
"ingest_url": "https://api.respawntech.dev/in/src_4f8a2c6e1b9d3f7a5c0e8b2d6f4a1c9e7b3d5f0a2c8e6b4d",
"has_verification_secret": true,
"event_type_path": "type",
"dedupe_key_path": "id",
"forward_original_headers": false,
"is_active": true,
"created_at": "2026-09-25T12:10:00Z",
"updated_at": "2026-09-25T12:10:00Z"
}
}Alterar fonte
/v1/tools/webhooks/sources/{id}- Custo
- Grátis
- Dry-run
- Não
- Autenticação
- qualquer um de
tools:webhooks:managetools:manage
Envie só os campos que quer mudar, inclusive provider (por exemplo, para passar uma fonte none a hmac_generic). Ao trocar para um provedor com assinatura, envie o verification_secret na mesma requisição, para a fonte não ficar sem secret (sem ele, ela responde 401). Para trocar só o secret, use Definir secret do provedor; um secret em branco é recusado. Uma fonte não muda de projeto.
curl -sS -X PATCH 'https://api.respawntech.dev/v1/tools/webhooks/sources/550e8400-e29b-41d4-a716-446655440000' \
-H "X-API-Token: $RESPAWN_API_TOKEN" \
-H 'Content-Type: application/json' \
-d '{ "provider": "hmac_generic", "forward_original_headers": true }'const response = await fetch('https://api.respawntech.dev/v1/tools/webhooks/sources/550e8400-e29b-41d4-a716-446655440000', {
method: 'PATCH',
headers: {
'X-API-Token': process.env.RESPAWN_API_TOKEN,
'Content-Type': 'application/json',
},
body: JSON.stringify({ "provider": "hmac_generic", "forward_original_headers": true }),
});
if (!response.ok) throw new Error((await response.json()).error.code);
console.log(await response.json());import os
import requests
response = requests.patch(
"https://api.respawntech.dev/v1/tools/webhooks/sources/550e8400-e29b-41d4-a716-446655440000",
headers={
"X-API-Token": os.environ["RESPAWN_API_TOKEN"],
},
json={ "provider": "hmac_generic", "forward_original_headers": True },
timeout=30,
)
if not response.ok:
raise RuntimeError(response.json()["error"]["code"])
print(response.json())<?php
$ch = curl_init('https://api.respawntech.dev/v1/tools/webhooks/sources/550e8400-e29b-41d4-a716-446655440000');
curl_setopt_array($ch, [
CURLOPT_CUSTOMREQUEST => 'PATCH',
CURLOPT_HTTPHEADER => [
'X-API-Token: ' . getenv('RESPAWN_API_TOKEN'),
'Content-Type: application/json',
],
CURLOPT_POSTFIELDS => <<<'JSON'
{ "provider": "hmac_generic", "forward_original_headers": true }
JSON,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_TIMEOUT => 30,
]);
$body = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_RESPONSE_CODE);
if ($status >= 400) {
throw new RuntimeException(json_decode($body, true)['error']['code']);
}
print_r(json_decode($body, true));package main
import (
"fmt"
"io"
"net/http"
"os"
"strings"
"time"
)
func main() {
req, err := http.NewRequest("PATCH", "https://api.respawntech.dev/v1/tools/webhooks/sources/550e8400-e29b-41d4-a716-446655440000", strings.NewReader(`{ "provider": "hmac_generic", "forward_original_headers": true }`))
if err != nil {
panic(err)
}
req.Header.Set("X-API-Token", os.Getenv("RESPAWN_API_TOKEN"))
req.Header.Set("Content-Type", "application/json")
client := &http.Client{Timeout: 30 * time.Second}
res, err := client.Do(req)
if err != nil {
panic(err)
}
defer res.Body.Close()
data, err := io.ReadAll(res.Body)
if err != nil {
panic(err)
}
if res.StatusCode >= 400 {
panic(string(data))
}
fmt.Println(string(data))
}Resposta 200
{
"success": true,
"trace_id": "550e8400-e29b-41d4-a716-446655440000",
"data": {
"id": "a6d1f8c3-2e9b-4c7a-9f5d-3b0e6a1c8d27",
"project_id": "3e7c1a9f-5b2d-4c8e-a6f0-9d1b4e7a2c53",
"name": "Stripe produção",
"provider": "stripe",
"ingest_url": "https://api.respawntech.dev/in/src_4f8a2c6e1b9d3f7a5c0e8b2d6f4a1c9e7b3d5f0a2c8e6b4d",
"has_verification_secret": true,
"event_type_path": "type",
"dedupe_key_path": "id",
"forward_original_headers": true,
"is_active": true,
"created_at": "2026-09-25T12:10:00Z",
"updated_at": "2026-09-25T13:00:00Z"
}
}Excluir fonte
/v1/tools/webhooks/sources/{id}- Custo
- Grátis
- Dry-run
- Não
- Autenticação
- qualquer um de
tools:webhooks:managetools:manage
As rotas da fonte são excluídas junto e a ingest_url para de responder.
curl -sS -X DELETE 'https://api.respawntech.dev/v1/tools/webhooks/sources/550e8400-e29b-41d4-a716-446655440000' \
-H "X-API-Token: $RESPAWN_API_TOKEN"const response = await fetch('https://api.respawntech.dev/v1/tools/webhooks/sources/550e8400-e29b-41d4-a716-446655440000', {
method: 'DELETE',
headers: {
'X-API-Token': process.env.RESPAWN_API_TOKEN,
},
});
if (!response.ok) throw new Error((await response.json()).error.code);
console.log(await response.json());import os
import requests
response = requests.delete(
"https://api.respawntech.dev/v1/tools/webhooks/sources/550e8400-e29b-41d4-a716-446655440000",
headers={
"X-API-Token": os.environ["RESPAWN_API_TOKEN"],
},
timeout=30,
)
if not response.ok:
raise RuntimeError(response.json()["error"]["code"])
print(response.json())<?php
$ch = curl_init('https://api.respawntech.dev/v1/tools/webhooks/sources/550e8400-e29b-41d4-a716-446655440000');
curl_setopt_array($ch, [
CURLOPT_CUSTOMREQUEST => 'DELETE',
CURLOPT_HTTPHEADER => [
'X-API-Token: ' . getenv('RESPAWN_API_TOKEN'),
],
CURLOPT_RETURNTRANSFER => true,
CURLOPT_TIMEOUT => 30,
]);
$body = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_RESPONSE_CODE);
if ($status >= 400) {
throw new RuntimeException(json_decode($body, true)['error']['code']);
}
print_r(json_decode($body, true));package main
import (
"fmt"
"io"
"net/http"
"os"
"time"
)
func main() {
req, err := http.NewRequest("DELETE", "https://api.respawntech.dev/v1/tools/webhooks/sources/550e8400-e29b-41d4-a716-446655440000", nil)
if err != nil {
panic(err)
}
req.Header.Set("X-API-Token", os.Getenv("RESPAWN_API_TOKEN"))
client := &http.Client{Timeout: 30 * time.Second}
res, err := client.Do(req)
if err != nil {
panic(err)
}
defer res.Body.Close()
data, err := io.ReadAll(res.Body)
if err != nil {
panic(err)
}
if res.StatusCode >= 400 {
panic(string(data))
}
fmt.Println(string(data))
}Resposta 200
{
"success": true,
"trace_id": "550e8400-e29b-41d4-a716-446655440000",
"data": {
"deleted": true
}
}Trocar URL de ingestão
/v1/tools/webhooks/sources/{id}/rotate-token- Custo
- Grátis
- Dry-run
- Não
- Autenticação
- qualquer um de
tools:webhooks:managetools:manage
Gera uma nova ingest_url; a anterior deixa de funcionar na hora. Atualize a URL no provedor logo em seguida.
curl -sS -X POST 'https://api.respawntech.dev/v1/tools/webhooks/sources/550e8400-e29b-41d4-a716-446655440000/rotate-token' \
-H "X-API-Token: $RESPAWN_API_TOKEN"const response = await fetch('https://api.respawntech.dev/v1/tools/webhooks/sources/550e8400-e29b-41d4-a716-446655440000/rotate-token', {
method: 'POST',
headers: {
'X-API-Token': process.env.RESPAWN_API_TOKEN,
},
});
if (!response.ok) throw new Error((await response.json()).error.code);
console.log(await response.json());import os
import requests
response = requests.post(
"https://api.respawntech.dev/v1/tools/webhooks/sources/550e8400-e29b-41d4-a716-446655440000/rotate-token",
headers={
"X-API-Token": os.environ["RESPAWN_API_TOKEN"],
},
timeout=30,
)
if not response.ok:
raise RuntimeError(response.json()["error"]["code"])
print(response.json())<?php
$ch = curl_init('https://api.respawntech.dev/v1/tools/webhooks/sources/550e8400-e29b-41d4-a716-446655440000/rotate-token');
curl_setopt_array($ch, [
CURLOPT_CUSTOMREQUEST => 'POST',
CURLOPT_HTTPHEADER => [
'X-API-Token: ' . getenv('RESPAWN_API_TOKEN'),
],
CURLOPT_RETURNTRANSFER => true,
CURLOPT_TIMEOUT => 30,
]);
$body = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_RESPONSE_CODE);
if ($status >= 400) {
throw new RuntimeException(json_decode($body, true)['error']['code']);
}
print_r(json_decode($body, true));package main
import (
"fmt"
"io"
"net/http"
"os"
"time"
)
func main() {
req, err := http.NewRequest("POST", "https://api.respawntech.dev/v1/tools/webhooks/sources/550e8400-e29b-41d4-a716-446655440000/rotate-token", nil)
if err != nil {
panic(err)
}
req.Header.Set("X-API-Token", os.Getenv("RESPAWN_API_TOKEN"))
client := &http.Client{Timeout: 30 * time.Second}
res, err := client.Do(req)
if err != nil {
panic(err)
}
defer res.Body.Close()
data, err := io.ReadAll(res.Body)
if err != nil {
panic(err)
}
if res.StatusCode >= 400 {
panic(string(data))
}
fmt.Println(string(data))
}Resposta 200 — a ingest_url anterior para de funcionar na hora
{
"success": true,
"trace_id": "550e8400-e29b-41d4-a716-446655440000",
"data": {
"id": "a6d1f8c3-2e9b-4c7a-9f5d-3b0e6a1c8d27",
"project_id": "3e7c1a9f-5b2d-4c8e-a6f0-9d1b4e7a2c53",
"name": "Stripe produção",
"provider": "stripe",
"ingest_url": "https://api.respawntech.dev/in/src_9c1e7a3f5b2d8e4a6c0f9b1d3e7a5c2f8b4d6e0a1c3f5b7d",
"has_verification_secret": true,
"event_type_path": "type",
"dedupe_key_path": "id",
"forward_original_headers": false,
"is_active": true,
"created_at": "2026-09-25T12:10:00Z",
"updated_at": "2026-09-25T13:00:00Z"
}
}Definir secret do provedor
/v1/tools/webhooks/sources/{id}/verification-secret- Custo
- Grátis
- Dry-run
- Não
- Autenticação
- qualquer um de
tools:webhooks:managetools:manage
Um secret vazio só é aceito com provider: "none".
curl -sS -X PUT 'https://api.respawntech.dev/v1/tools/webhooks/sources/550e8400-e29b-41d4-a716-446655440000/verification-secret' \
-H "X-API-Token: $RESPAWN_API_TOKEN" \
-H 'Content-Type: application/json' \
-d '{ "secret": "whsec_..." }'const response = await fetch('https://api.respawntech.dev/v1/tools/webhooks/sources/550e8400-e29b-41d4-a716-446655440000/verification-secret', {
method: 'PUT',
headers: {
'X-API-Token': process.env.RESPAWN_API_TOKEN,
'Content-Type': 'application/json',
},
body: JSON.stringify({ "secret": "whsec_..." }),
});
if (!response.ok) throw new Error((await response.json()).error.code);
console.log(await response.json());import os
import requests
response = requests.put(
"https://api.respawntech.dev/v1/tools/webhooks/sources/550e8400-e29b-41d4-a716-446655440000/verification-secret",
headers={
"X-API-Token": os.environ["RESPAWN_API_TOKEN"],
},
json={ "secret": "whsec_..." },
timeout=30,
)
if not response.ok:
raise RuntimeError(response.json()["error"]["code"])
print(response.json())<?php
$ch = curl_init('https://api.respawntech.dev/v1/tools/webhooks/sources/550e8400-e29b-41d4-a716-446655440000/verification-secret');
curl_setopt_array($ch, [
CURLOPT_CUSTOMREQUEST => 'PUT',
CURLOPT_HTTPHEADER => [
'X-API-Token: ' . getenv('RESPAWN_API_TOKEN'),
'Content-Type: application/json',
],
CURLOPT_POSTFIELDS => <<<'JSON'
{ "secret": "whsec_..." }
JSON,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_TIMEOUT => 30,
]);
$body = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_RESPONSE_CODE);
if ($status >= 400) {
throw new RuntimeException(json_decode($body, true)['error']['code']);
}
print_r(json_decode($body, true));package main
import (
"fmt"
"io"
"net/http"
"os"
"strings"
"time"
)
func main() {
req, err := http.NewRequest("PUT", "https://api.respawntech.dev/v1/tools/webhooks/sources/550e8400-e29b-41d4-a716-446655440000/verification-secret", strings.NewReader(`{ "secret": "whsec_..." }`))
if err != nil {
panic(err)
}
req.Header.Set("X-API-Token", os.Getenv("RESPAWN_API_TOKEN"))
req.Header.Set("Content-Type", "application/json")
client := &http.Client{Timeout: 30 * time.Second}
res, err := client.Do(req)
if err != nil {
panic(err)
}
defer res.Body.Close()
data, err := io.ReadAll(res.Body)
if err != nil {
panic(err)
}
if res.StatusCode >= 400 {
panic(string(data))
}
fmt.Println(string(data))
}Resposta 200
{
"success": true,
"trace_id": "550e8400-e29b-41d4-a716-446655440000",
"data": {
"id": "a6d1f8c3-2e9b-4c7a-9f5d-3b0e6a1c8d27",
"project_id": "3e7c1a9f-5b2d-4c8e-a6f0-9d1b4e7a2c53",
"name": "Stripe produção",
"provider": "stripe",
"ingest_url": "https://api.respawntech.dev/in/src_4f8a2c6e1b9d3f7a5c0e8b2d6f4a1c9e7b3d5f0a2c8e6b4d",
"has_verification_secret": true,
"event_type_path": "type",
"dedupe_key_path": "id",
"forward_original_headers": false,
"is_active": true,
"created_at": "2026-09-25T12:10:00Z",
"updated_at": "2026-09-25T13:00:00Z"
}
}Consultar último evento recebido
/v1/tools/webhooks/sources/{id}/sample- Custo
- Grátis
- Dry-run
- Não
- Autenticação
- qualquer um de
tools:webhooks:readtools:webhooks:managetools:readtools:manage
Devolve o último corpo recebido pela fonte, com o tipo de evento lido e a hora. Útil para descobrir os campos do provedor antes de configurar event_type_path, dedupe_key_path e os filtros das rotas.
curl -sS -X GET 'https://api.respawntech.dev/v1/tools/webhooks/sources/550e8400-e29b-41d4-a716-446655440000/sample' \
-H "X-API-Token: $RESPAWN_API_TOKEN"const response = await fetch('https://api.respawntech.dev/v1/tools/webhooks/sources/550e8400-e29b-41d4-a716-446655440000/sample', {
method: 'GET',
headers: {
'X-API-Token': process.env.RESPAWN_API_TOKEN,
},
});
if (!response.ok) throw new Error((await response.json()).error.code);
console.log(await response.json());import os
import requests
response = requests.get(
"https://api.respawntech.dev/v1/tools/webhooks/sources/550e8400-e29b-41d4-a716-446655440000/sample",
headers={
"X-API-Token": os.environ["RESPAWN_API_TOKEN"],
},
timeout=30,
)
if not response.ok:
raise RuntimeError(response.json()["error"]["code"])
print(response.json())<?php
$ch = curl_init('https://api.respawntech.dev/v1/tools/webhooks/sources/550e8400-e29b-41d4-a716-446655440000/sample');
curl_setopt_array($ch, [
CURLOPT_CUSTOMREQUEST => 'GET',
CURLOPT_HTTPHEADER => [
'X-API-Token: ' . getenv('RESPAWN_API_TOKEN'),
],
CURLOPT_RETURNTRANSFER => true,
CURLOPT_TIMEOUT => 30,
]);
$body = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_RESPONSE_CODE);
if ($status >= 400) {
throw new RuntimeException(json_decode($body, true)['error']['code']);
}
print_r(json_decode($body, true));package main
import (
"fmt"
"io"
"net/http"
"os"
"time"
)
func main() {
req, err := http.NewRequest("GET", "https://api.respawntech.dev/v1/tools/webhooks/sources/550e8400-e29b-41d4-a716-446655440000/sample", nil)
if err != nil {
panic(err)
}
req.Header.Set("X-API-Token", os.Getenv("RESPAWN_API_TOKEN"))
client := &http.Client{Timeout: 30 * time.Second}
res, err := client.Do(req)
if err != nil {
panic(err)
}
defer res.Body.Close()
data, err := io.ReadAll(res.Body)
if err != nil {
panic(err)
}
if res.StatusCode >= 400 {
panic(string(data))
}
fmt.Println(string(data))
}Resposta 200 — data é null enquanto a fonte não recebeu nada; um corpo que não é JSON vem como texto
{
"success": true,
"trace_id": "550e8400-e29b-41d4-a716-446655440000",
"data": {
"event_type": "checkout.session.completed",
"body": {
"id": "evt_1Q2w3E",
"type": "checkout.session.completed",
"data": {
"object": {
"id": "cs_test_a1B2"
}
}
},
"received_at": "2026-09-25T12:20:00Z"
}
}Enviar evento de teste pela fonte
/v1/tools/webhooks/sources/{id}/test- Custo
- Grátis
- Dry-run
- Não
- Autenticação
- qualquer um de
tools:webhooks:managetools:manage
Faz um evento passar pela fonte como se o provedor o tivesse enviado: o tipo é lido em event_type_path e as rotas (com os filtros) decidem para onde ele vai. O corpo é o último recebido pela fonte ou, se ela ainda não recebeu nada, um exemplo do provedor. event_type (opcional) é gravado no caminho de event_type_path; se o corpo não tiver esse campo, o tipo usado é test.event.
É grátis e fica fora da franquia mensal. Não verifica assinatura nem deduplica, mas conta no limite de 600 requisições por minuto da fonte.
curl -sS -X POST 'https://api.respawntech.dev/v1/tools/webhooks/sources/550e8400-e29b-41d4-a716-446655440000/test' \
-H "X-API-Token: $RESPAWN_API_TOKEN" \
-H 'Content-Type: application/json' \
-d '{ "event_type": "invoice.paid" }'const response = await fetch('https://api.respawntech.dev/v1/tools/webhooks/sources/550e8400-e29b-41d4-a716-446655440000/test', {
method: 'POST',
headers: {
'X-API-Token': process.env.RESPAWN_API_TOKEN,
'Content-Type': 'application/json',
},
body: JSON.stringify({ "event_type": "invoice.paid" }),
});
if (!response.ok) throw new Error((await response.json()).error.code);
console.log(await response.json());import os
import requests
response = requests.post(
"https://api.respawntech.dev/v1/tools/webhooks/sources/550e8400-e29b-41d4-a716-446655440000/test",
headers={
"X-API-Token": os.environ["RESPAWN_API_TOKEN"],
},
json={ "event_type": "invoice.paid" },
timeout=30,
)
if not response.ok:
raise RuntimeError(response.json()["error"]["code"])
print(response.json())<?php
$ch = curl_init('https://api.respawntech.dev/v1/tools/webhooks/sources/550e8400-e29b-41d4-a716-446655440000/test');
curl_setopt_array($ch, [
CURLOPT_CUSTOMREQUEST => 'POST',
CURLOPT_HTTPHEADER => [
'X-API-Token: ' . getenv('RESPAWN_API_TOKEN'),
'Content-Type: application/json',
],
CURLOPT_POSTFIELDS => <<<'JSON'
{ "event_type": "invoice.paid" }
JSON,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_TIMEOUT => 30,
]);
$body = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_RESPONSE_CODE);
if ($status >= 400) {
throw new RuntimeException(json_decode($body, true)['error']['code']);
}
print_r(json_decode($body, true));package main
import (
"fmt"
"io"
"net/http"
"os"
"strings"
"time"
)
func main() {
req, err := http.NewRequest("POST", "https://api.respawntech.dev/v1/tools/webhooks/sources/550e8400-e29b-41d4-a716-446655440000/test", strings.NewReader(`{ "event_type": "invoice.paid" }`))
if err != nil {
panic(err)
}
req.Header.Set("X-API-Token", os.Getenv("RESPAWN_API_TOKEN"))
req.Header.Set("Content-Type", "application/json")
client := &http.Client{Timeout: 30 * time.Second}
res, err := client.Do(req)
if err != nil {
panic(err)
}
defer res.Body.Close()
data, err := io.ReadAll(res.Body)
if err != nil {
panic(err)
}
if res.StatusCode >= 400 {
panic(string(data))
}
fmt.Println(string(data))
}Resposta 202
{
"success": true,
"trace_id": "550e8400-e29b-41d4-a716-446655440000",
"data": {
"message_id": "c1a7e5d3-9b2f-4a6e-8d4c-0f3b7e1a5c92",
"event_type": "invoice.paid",
"deliveries": 1
}
}Criar rota
/v1/tools/webhooks/routes- Custo
- Grátis
- Dry-run
- Não
- Autenticação
- qualquer um de
tools:webhooks:managetools:manage
Liga uma fonte a um endpoint do mesmo projeto (fonte e endpoint de projetos diferentes respondem 400). event_types restringe a rota a esses tipos, até 100; vazio, a rota leva todos os eventos da fonte. Até 50 rotas por fonte.
curl -sS -X POST 'https://api.respawntech.dev/v1/tools/webhooks/routes' \
-H "X-API-Token: $RESPAWN_API_TOKEN" \
-H 'Content-Type: application/json' \
-d '{
"source_id": "a6d1f8c3-2e9b-4c7a-9f5d-3b0e6a1c8d27",
"endpoint_id": "8d2f4a61-3c7b-4e9a-b1d5-6f0e2a9c7b34",
"event_types": ["checkout.session.completed", "invoice.paid"]
}'const response = await fetch('https://api.respawntech.dev/v1/tools/webhooks/routes', {
method: 'POST',
headers: {
'X-API-Token': process.env.RESPAWN_API_TOKEN,
'Content-Type': 'application/json',
},
body: JSON.stringify({
"source_id": "a6d1f8c3-2e9b-4c7a-9f5d-3b0e6a1c8d27",
"endpoint_id": "8d2f4a61-3c7b-4e9a-b1d5-6f0e2a9c7b34",
"event_types": ["checkout.session.completed", "invoice.paid"]
}),
});
if (!response.ok) throw new Error((await response.json()).error.code);
console.log(await response.json());import os
import requests
response = requests.post(
"https://api.respawntech.dev/v1/tools/webhooks/routes",
headers={
"X-API-Token": os.environ["RESPAWN_API_TOKEN"],
},
json={
"source_id": "a6d1f8c3-2e9b-4c7a-9f5d-3b0e6a1c8d27",
"endpoint_id": "8d2f4a61-3c7b-4e9a-b1d5-6f0e2a9c7b34",
"event_types": ["checkout.session.completed", "invoice.paid"]
},
timeout=30,
)
if not response.ok:
raise RuntimeError(response.json()["error"]["code"])
print(response.json())<?php
$ch = curl_init('https://api.respawntech.dev/v1/tools/webhooks/routes');
curl_setopt_array($ch, [
CURLOPT_CUSTOMREQUEST => 'POST',
CURLOPT_HTTPHEADER => [
'X-API-Token: ' . getenv('RESPAWN_API_TOKEN'),
'Content-Type: application/json',
],
CURLOPT_POSTFIELDS => <<<'JSON'
{
"source_id": "a6d1f8c3-2e9b-4c7a-9f5d-3b0e6a1c8d27",
"endpoint_id": "8d2f4a61-3c7b-4e9a-b1d5-6f0e2a9c7b34",
"event_types": ["checkout.session.completed", "invoice.paid"]
}
JSON,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_TIMEOUT => 30,
]);
$body = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_RESPONSE_CODE);
if ($status >= 400) {
throw new RuntimeException(json_decode($body, true)['error']['code']);
}
print_r(json_decode($body, true));package main
import (
"fmt"
"io"
"net/http"
"os"
"strings"
"time"
)
func main() {
req, err := http.NewRequest("POST", "https://api.respawntech.dev/v1/tools/webhooks/routes", strings.NewReader(`{
"source_id": "a6d1f8c3-2e9b-4c7a-9f5d-3b0e6a1c8d27",
"endpoint_id": "8d2f4a61-3c7b-4e9a-b1d5-6f0e2a9c7b34",
"event_types": ["checkout.session.completed", "invoice.paid"]
}`))
if err != nil {
panic(err)
}
req.Header.Set("X-API-Token", os.Getenv("RESPAWN_API_TOKEN"))
req.Header.Set("Content-Type", "application/json")
client := &http.Client{Timeout: 30 * time.Second}
res, err := client.Do(req)
if err != nil {
panic(err)
}
defer res.Body.Close()
data, err := io.ReadAll(res.Body)
if err != nil {
panic(err)
}
if res.StatusCode >= 400 {
panic(string(data))
}
fmt.Println(string(data))
}Resposta 201
{
"success": true,
"trace_id": "550e8400-e29b-41d4-a716-446655440000",
"data": {
"id": "f2c8a5e1-6b3d-4f9a-8e7c-1a4d9b2e5f60",
"project_id": "3e7c1a9f-5b2d-4c8e-a6f0-9d1b4e7a2c53",
"source_id": "a6d1f8c3-2e9b-4c7a-9f5d-3b0e6a1c8d27",
"endpoint_id": "8d2f4a61-3c7b-4e9a-b1d5-6f0e2a9c7b34",
"event_types": [
"checkout.session.completed",
"invoice.paid"
],
"is_active": true,
"created_at": "2026-09-25T12:12:00Z"
}
}Listar rotas
/v1/tools/webhooks/routes- Custo
- Grátis
- Dry-run
- Não
- Autenticação
- qualquer um de
tools:webhooks:readtools:webhooks:managetools:readtools:manage
Filtros opcionais por source_id e project_id.
curl -sS -X GET 'https://api.respawntech.dev/v1/tools/webhooks/routes?source_id=a6d1f8c3-2e9b-4c7a-9f5d-3b0e6a1c8d27' \
-H "X-API-Token: $RESPAWN_API_TOKEN"const response = await fetch('https://api.respawntech.dev/v1/tools/webhooks/routes?source_id=a6d1f8c3-2e9b-4c7a-9f5d-3b0e6a1c8d27', {
method: 'GET',
headers: {
'X-API-Token': process.env.RESPAWN_API_TOKEN,
},
});
if (!response.ok) throw new Error((await response.json()).error.code);
console.log(await response.json());import os
import requests
response = requests.get(
"https://api.respawntech.dev/v1/tools/webhooks/routes?source_id=a6d1f8c3-2e9b-4c7a-9f5d-3b0e6a1c8d27",
headers={
"X-API-Token": os.environ["RESPAWN_API_TOKEN"],
},
timeout=30,
)
if not response.ok:
raise RuntimeError(response.json()["error"]["code"])
print(response.json())<?php
$ch = curl_init('https://api.respawntech.dev/v1/tools/webhooks/routes?source_id=a6d1f8c3-2e9b-4c7a-9f5d-3b0e6a1c8d27');
curl_setopt_array($ch, [
CURLOPT_CUSTOMREQUEST => 'GET',
CURLOPT_HTTPHEADER => [
'X-API-Token: ' . getenv('RESPAWN_API_TOKEN'),
],
CURLOPT_RETURNTRANSFER => true,
CURLOPT_TIMEOUT => 30,
]);
$body = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_RESPONSE_CODE);
if ($status >= 400) {
throw new RuntimeException(json_decode($body, true)['error']['code']);
}
print_r(json_decode($body, true));package main
import (
"fmt"
"io"
"net/http"
"os"
"time"
)
func main() {
req, err := http.NewRequest("GET", "https://api.respawntech.dev/v1/tools/webhooks/routes?source_id=a6d1f8c3-2e9b-4c7a-9f5d-3b0e6a1c8d27", nil)
if err != nil {
panic(err)
}
req.Header.Set("X-API-Token", os.Getenv("RESPAWN_API_TOKEN"))
client := &http.Client{Timeout: 30 * time.Second}
res, err := client.Do(req)
if err != nil {
panic(err)
}
defer res.Body.Close()
data, err := io.ReadAll(res.Body)
if err != nil {
panic(err)
}
if res.StatusCode >= 400 {
panic(string(data))
}
fmt.Println(string(data))
}Resposta 200
{
"success": true,
"trace_id": "550e8400-e29b-41d4-a716-446655440000",
"data": {
"routes": [
{
"id": "f2c8a5e1-6b3d-4f9a-8e7c-1a4d9b2e5f60",
"project_id": "3e7c1a9f-5b2d-4c8e-a6f0-9d1b4e7a2c53",
"source_id": "a6d1f8c3-2e9b-4c7a-9f5d-3b0e6a1c8d27",
"endpoint_id": "8d2f4a61-3c7b-4e9a-b1d5-6f0e2a9c7b34",
"event_types": [
"checkout.session.completed",
"invoice.paid"
],
"is_active": true,
"created_at": "2026-09-25T12:12:00Z"
}
]
}
}Alterar rota
/v1/tools/webhooks/routes/{id}- Custo
- Grátis
- Dry-run
- Não
- Autenticação
- qualquer um de
tools:webhooks:managetools:manage
Muda os tipos de evento da rota (lista vazia = todos) ou a pausa com is_active: false. Envie só os campos que quer mudar.
curl -sS -X PATCH 'https://api.respawntech.dev/v1/tools/webhooks/routes/550e8400-e29b-41d4-a716-446655440000' \
-H "X-API-Token: $RESPAWN_API_TOKEN" \
-H 'Content-Type: application/json' \
-d '{ "event_types": ["invoice.paid"], "is_active": false }'const response = await fetch('https://api.respawntech.dev/v1/tools/webhooks/routes/550e8400-e29b-41d4-a716-446655440000', {
method: 'PATCH',
headers: {
'X-API-Token': process.env.RESPAWN_API_TOKEN,
'Content-Type': 'application/json',
},
body: JSON.stringify({ "event_types": ["invoice.paid"], "is_active": false }),
});
if (!response.ok) throw new Error((await response.json()).error.code);
console.log(await response.json());import os
import requests
response = requests.patch(
"https://api.respawntech.dev/v1/tools/webhooks/routes/550e8400-e29b-41d4-a716-446655440000",
headers={
"X-API-Token": os.environ["RESPAWN_API_TOKEN"],
},
json={ "event_types": ["invoice.paid"], "is_active": False },
timeout=30,
)
if not response.ok:
raise RuntimeError(response.json()["error"]["code"])
print(response.json())<?php
$ch = curl_init('https://api.respawntech.dev/v1/tools/webhooks/routes/550e8400-e29b-41d4-a716-446655440000');
curl_setopt_array($ch, [
CURLOPT_CUSTOMREQUEST => 'PATCH',
CURLOPT_HTTPHEADER => [
'X-API-Token: ' . getenv('RESPAWN_API_TOKEN'),
'Content-Type: application/json',
],
CURLOPT_POSTFIELDS => <<<'JSON'
{ "event_types": ["invoice.paid"], "is_active": false }
JSON,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_TIMEOUT => 30,
]);
$body = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_RESPONSE_CODE);
if ($status >= 400) {
throw new RuntimeException(json_decode($body, true)['error']['code']);
}
print_r(json_decode($body, true));package main
import (
"fmt"
"io"
"net/http"
"os"
"strings"
"time"
)
func main() {
req, err := http.NewRequest("PATCH", "https://api.respawntech.dev/v1/tools/webhooks/routes/550e8400-e29b-41d4-a716-446655440000", strings.NewReader(`{ "event_types": ["invoice.paid"], "is_active": false }`))
if err != nil {
panic(err)
}
req.Header.Set("X-API-Token", os.Getenv("RESPAWN_API_TOKEN"))
req.Header.Set("Content-Type", "application/json")
client := &http.Client{Timeout: 30 * time.Second}
res, err := client.Do(req)
if err != nil {
panic(err)
}
defer res.Body.Close()
data, err := io.ReadAll(res.Body)
if err != nil {
panic(err)
}
if res.StatusCode >= 400 {
panic(string(data))
}
fmt.Println(string(data))
}Resposta 200
{
"success": true,
"trace_id": "550e8400-e29b-41d4-a716-446655440000",
"data": {
"id": "f2c8a5e1-6b3d-4f9a-8e7c-1a4d9b2e5f60",
"project_id": "3e7c1a9f-5b2d-4c8e-a6f0-9d1b4e7a2c53",
"source_id": "a6d1f8c3-2e9b-4c7a-9f5d-3b0e6a1c8d27",
"endpoint_id": "8d2f4a61-3c7b-4e9a-b1d5-6f0e2a9c7b34",
"event_types": [
"invoice.paid"
],
"is_active": false,
"created_at": "2026-09-25T12:12:00Z"
}
}Excluir rota
/v1/tools/webhooks/routes/{id}- Custo
- Grátis
- Dry-run
- Não
- Autenticação
- qualquer um de
tools:webhooks:managetools:manage
curl -sS -X DELETE 'https://api.respawntech.dev/v1/tools/webhooks/routes/550e8400-e29b-41d4-a716-446655440000' \
-H "X-API-Token: $RESPAWN_API_TOKEN"const response = await fetch('https://api.respawntech.dev/v1/tools/webhooks/routes/550e8400-e29b-41d4-a716-446655440000', {
method: 'DELETE',
headers: {
'X-API-Token': process.env.RESPAWN_API_TOKEN,
},
});
if (!response.ok) throw new Error((await response.json()).error.code);
console.log(await response.json());import os
import requests
response = requests.delete(
"https://api.respawntech.dev/v1/tools/webhooks/routes/550e8400-e29b-41d4-a716-446655440000",
headers={
"X-API-Token": os.environ["RESPAWN_API_TOKEN"],
},
timeout=30,
)
if not response.ok:
raise RuntimeError(response.json()["error"]["code"])
print(response.json())<?php
$ch = curl_init('https://api.respawntech.dev/v1/tools/webhooks/routes/550e8400-e29b-41d4-a716-446655440000');
curl_setopt_array($ch, [
CURLOPT_CUSTOMREQUEST => 'DELETE',
CURLOPT_HTTPHEADER => [
'X-API-Token: ' . getenv('RESPAWN_API_TOKEN'),
],
CURLOPT_RETURNTRANSFER => true,
CURLOPT_TIMEOUT => 30,
]);
$body = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_RESPONSE_CODE);
if ($status >= 400) {
throw new RuntimeException(json_decode($body, true)['error']['code']);
}
print_r(json_decode($body, true));package main
import (
"fmt"
"io"
"net/http"
"os"
"time"
)
func main() {
req, err := http.NewRequest("DELETE", "https://api.respawntech.dev/v1/tools/webhooks/routes/550e8400-e29b-41d4-a716-446655440000", nil)
if err != nil {
panic(err)
}
req.Header.Set("X-API-Token", os.Getenv("RESPAWN_API_TOKEN"))
client := &http.Client{Timeout: 30 * time.Second}
res, err := client.Do(req)
if err != nil {
panic(err)
}
defer res.Body.Close()
data, err := io.ReadAll(res.Body)
if err != nil {
panic(err)
}
if res.StatusCode >= 400 {
panic(string(data))
}
fmt.Println(string(data))
}Resposta 200
{
"success": true,
"trace_id": "550e8400-e29b-41d4-a716-446655440000",
"data": {
"deleted": true
}
}Receber evento de um provedor
/in/{token}- Custo
- Por entrega redistribuída, além da franquia mensal
- Dry-run
- Não
- Autenticação
- Pública (sem token)
É a URL que o provedor chama; você não precisa chamá-la. O corpo é guardado e repassado byte a byte, com o Content-Type original. As respostas são pensadas para o provedor decidir se tenta de novo:
| Status | Quando |
|---|---|
200 | Aceito, duplicado (reenvio do provedor) ou sem rota para o tipo. |
401 | Assinatura inválida, ou secret do provedor ainda não definido; nada é gravado. |
404 | Fonte desconhecida ou desativada, ou ferramenta não liberada na conta. |
429 | Mais de 600 requisições por minuto para a fonte. |
5xx | Falha ao gravar; o provedor deve tentar de novo. |
curl -sS -X POST 'https://api.respawntech.dev/in/src_4f8a2c6e1b9d3f7a5c0e8b2d6f4a1c9e7b3d5f0a2c8e6b4d' \
-H 'Stripe-Signature: t=1790338000,v1=5257a869…' \
-H 'Content-Type: application/json' \
-d '{ "id": "evt_1Q2w3E", "type": "checkout.session.completed", "data": { "object": { "id": "cs_test_a1B2" } } }'const response = await fetch('https://api.respawntech.dev/in/src_4f8a2c6e1b9d3f7a5c0e8b2d6f4a1c9e7b3d5f0a2c8e6b4d', {
method: 'POST',
headers: {
'Stripe-Signature': 't=1790338000,v1=5257a869…',
'Content-Type': 'application/json',
},
body: JSON.stringify({ "id": "evt_1Q2w3E", "type": "checkout.session.completed", "data": { "object": { "id": "cs_test_a1B2" } } }),
});
if (!response.ok) throw new Error((await response.json()).error.code);
console.log(await response.json());import os
import requests
response = requests.post(
"https://api.respawntech.dev/in/src_4f8a2c6e1b9d3f7a5c0e8b2d6f4a1c9e7b3d5f0a2c8e6b4d",
headers={
"Stripe-Signature": "t=1790338000,v1=5257a869…",
},
json={ "id": "evt_1Q2w3E", "type": "checkout.session.completed", "data": { "object": { "id": "cs_test_a1B2" } } },
timeout=30,
)
if not response.ok:
raise RuntimeError(response.json()["error"]["code"])
print(response.json())<?php
$ch = curl_init('https://api.respawntech.dev/in/src_4f8a2c6e1b9d3f7a5c0e8b2d6f4a1c9e7b3d5f0a2c8e6b4d');
curl_setopt_array($ch, [
CURLOPT_CUSTOMREQUEST => 'POST',
CURLOPT_HTTPHEADER => [
'Stripe-Signature: t=1790338000,v1=5257a869…',
'Content-Type: application/json',
],
CURLOPT_POSTFIELDS => <<<'JSON'
{ "id": "evt_1Q2w3E", "type": "checkout.session.completed", "data": { "object": { "id": "cs_test_a1B2" } } }
JSON,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_TIMEOUT => 30,
]);
$body = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_RESPONSE_CODE);
if ($status >= 400) {
throw new RuntimeException(json_decode($body, true)['error']['code']);
}
print_r(json_decode($body, true));package main
import (
"fmt"
"io"
"net/http"
"strings"
"time"
)
func main() {
req, err := http.NewRequest("POST", "https://api.respawntech.dev/in/src_4f8a2c6e1b9d3f7a5c0e8b2d6f4a1c9e7b3d5f0a2c8e6b4d", strings.NewReader(`{ "id": "evt_1Q2w3E", "type": "checkout.session.completed", "data": { "object": { "id": "cs_test_a1B2" } } }`))
if err != nil {
panic(err)
}
req.Header.Set("Stripe-Signature", "t=1790338000,v1=5257a869…")
req.Header.Set("Content-Type", "application/json")
client := &http.Client{Timeout: 30 * time.Second}
res, err := client.Do(req)
if err != nil {
panic(err)
}
defer res.Body.Close()
data, err := io.ReadAll(res.Body)
if err != nil {
panic(err)
}
if res.StatusCode >= 400 {
panic(string(data))
}
fmt.Println(string(data))
}Resposta 200 — resposta sem o envelope da API; 200 também para duplicatas e eventos sem rota. blocked conta as entregas esperando créditos
{
"message_id": "c1a7e5d3-9b2f-4a6e-8d4c-0f3b7e1a5c92",
"event_type": "checkout.session.completed",
"deliveries": 1
}Exemplo: Stripe
Crie um endpoint para cada serviço seu que precisa dos eventos do Stripe. Os eventos chegam pelas rotas, então events pode ficar vazio.
Crie uma fonte com provider: "stripe", event_type_path: "type" e dedupe_key_path: "id", ainda sem o secret.
No painel do Stripe, adicione um endpoint de webhook com a ingest_url e copie o signing secret (whsec_…) para Definir secret do provedor.
Crie uma rota da fonte para cada endpoint, com event_types quando um serviço só precisa de alguns eventos. Confira o caminho com Enviar evento de teste pela fonte.
Os seus serviços passam a receber os eventos assinados pela plataforma (valide com o secret de cada endpoint). Se preferirem validar a assinatura do próprio Stripe, ative forward_original_headers na fonte: o Stripe-Signature e o corpo idêntico chegam junto.
Sem créditos
O provedor nunca é recusado por falta de saldo. As entregas que seriam cobradas ficam com status blocked_no_credits (contadas em blocked na resposta ao provedor) e são cobradas e liberadas automaticamente assim que a conta tiver créditos. Se o saldo não voltar em 7 dias, elas terminam como failed.
Canvas do dashboard
No dashboard, cada projeto aparece como um canvas com as fontes, os endpoints e as rotas entre eles. Estas rotas alimentam essa tela; numa integração pela API, as listagens acima costumam bastar. As três aceitam ?project_id= (padrão: o projeto padrão).
Consultar mapa do fluxo
/v1/tools/webhooks/graph- Custo
- Grátis
- Dry-run
- Não
- Autenticação
- qualquer um de
tools:webhooks:readtools:webhooks:managetools:readtools:manage
Fontes, endpoints e rotas do projeto com os contadores das últimas 24 horas: por fonte, mensagens recebidas, a última e os tipos de evento recentes; por endpoint e por rota, entregas, falhas e o status da última.
curl -sS -X GET 'https://api.respawntech.dev/v1/tools/webhooks/graph?project_id=3e7c1a9f-5b2d-4c8e-a6f0-9d1b4e7a2c53' \
-H "X-API-Token: $RESPAWN_API_TOKEN"const response = await fetch('https://api.respawntech.dev/v1/tools/webhooks/graph?project_id=3e7c1a9f-5b2d-4c8e-a6f0-9d1b4e7a2c53', {
method: 'GET',
headers: {
'X-API-Token': process.env.RESPAWN_API_TOKEN,
},
});
if (!response.ok) throw new Error((await response.json()).error.code);
console.log(await response.json());import os
import requests
response = requests.get(
"https://api.respawntech.dev/v1/tools/webhooks/graph?project_id=3e7c1a9f-5b2d-4c8e-a6f0-9d1b4e7a2c53",
headers={
"X-API-Token": os.environ["RESPAWN_API_TOKEN"],
},
timeout=30,
)
if not response.ok:
raise RuntimeError(response.json()["error"]["code"])
print(response.json())<?php
$ch = curl_init('https://api.respawntech.dev/v1/tools/webhooks/graph?project_id=3e7c1a9f-5b2d-4c8e-a6f0-9d1b4e7a2c53');
curl_setopt_array($ch, [
CURLOPT_CUSTOMREQUEST => 'GET',
CURLOPT_HTTPHEADER => [
'X-API-Token: ' . getenv('RESPAWN_API_TOKEN'),
],
CURLOPT_RETURNTRANSFER => true,
CURLOPT_TIMEOUT => 30,
]);
$body = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_RESPONSE_CODE);
if ($status >= 400) {
throw new RuntimeException(json_decode($body, true)['error']['code']);
}
print_r(json_decode($body, true));package main
import (
"fmt"
"io"
"net/http"
"os"
"time"
)
func main() {
req, err := http.NewRequest("GET", "https://api.respawntech.dev/v1/tools/webhooks/graph?project_id=3e7c1a9f-5b2d-4c8e-a6f0-9d1b4e7a2c53", nil)
if err != nil {
panic(err)
}
req.Header.Set("X-API-Token", os.Getenv("RESPAWN_API_TOKEN"))
client := &http.Client{Timeout: 30 * time.Second}
res, err := client.Do(req)
if err != nil {
panic(err)
}
defer res.Body.Close()
data, err := io.ReadAll(res.Body)
if err != nil {
panic(err)
}
if res.StatusCode >= 400 {
panic(string(data))
}
fmt.Println(string(data))
}Resposta 200 — contadores das últimas 24 horas
{
"success": true,
"trace_id": "550e8400-e29b-41d4-a716-446655440000",
"data": {
"sources": [
{
"id": "a6d1f8c3-2e9b-4c7a-9f5d-3b0e6a1c8d27",
"project_id": "3e7c1a9f-5b2d-4c8e-a6f0-9d1b4e7a2c53",
"name": "Stripe produção",
"provider": "stripe",
"ingest_url": "https://api.respawntech.dev/in/src_4f8a2c6e1b9d3f7a5c0e8b2d6f4a1c9e7b3d5f0a2c8e6b4d",
"has_verification_secret": true,
"event_type_path": "type",
"dedupe_key_path": "id",
"forward_original_headers": false,
"is_active": true,
"created_at": "2026-09-25T12:10:00Z",
"updated_at": "2026-09-25T12:10:00Z",
"messages_24h": 20,
"last_message_at": "2026-09-25T12:20:00Z",
"recent_event_types": [
"checkout.session.completed",
"invoice.paid"
]
}
],
"endpoints": [
{
"id": "8d2f4a61-3c7b-4e9a-b1d5-6f0e2a9c7b34",
"project_id": "3e7c1a9f-5b2d-4c8e-a6f0-9d1b4e7a2c53",
"url": "https://api.example.com/hooks/respawn",
"description": "Pedidos da loja",
"events": [
"order.paid",
"order.refunded"
],
"custom_headers": {
"X-Tenant": "loja-1"
},
"is_active": true,
"rate_limit_per_sec": 0,
"format": "raw",
"created_at": "2026-09-25T12:00:00Z",
"updated_at": "2026-09-25T12:00:00Z",
"stats": {
"deliveries_24h": 48,
"failed_24h": 1,
"last_delivery_at": "2026-09-25T12:05:06Z",
"last_status": "succeeded"
}
}
],
"routes": [
{
"id": "f2c8a5e1-6b3d-4f9a-8e7c-1a4d9b2e5f60",
"project_id": "3e7c1a9f-5b2d-4c8e-a6f0-9d1b4e7a2c53",
"source_id": "a6d1f8c3-2e9b-4c7a-9f5d-3b0e6a1c8d27",
"endpoint_id": "8d2f4a61-3c7b-4e9a-b1d5-6f0e2a9c7b34",
"event_types": [
"checkout.session.completed",
"invoice.paid"
],
"is_active": true,
"created_at": "2026-09-25T12:12:00Z",
"stats": {
"deliveries_24h": 48,
"failed_24h": 1,
"last_delivery_at": "2026-09-25T12:05:06Z",
"last_status": "succeeded"
}
}
]
}
}Consultar layout do canvas
/v1/tools/webhooks/layout- Custo
- Grátis
- Dry-run
- Não
- Autenticação
- qualquer um de
tools:webhooks:readtools:webhooks:managetools:readtools:manage
curl -sS -X GET 'https://api.respawntech.dev/v1/tools/webhooks/layout' \
-H "X-API-Token: $RESPAWN_API_TOKEN"const response = await fetch('https://api.respawntech.dev/v1/tools/webhooks/layout', {
method: 'GET',
headers: {
'X-API-Token': process.env.RESPAWN_API_TOKEN,
},
});
if (!response.ok) throw new Error((await response.json()).error.code);
console.log(await response.json());import os
import requests
response = requests.get(
"https://api.respawntech.dev/v1/tools/webhooks/layout",
headers={
"X-API-Token": os.environ["RESPAWN_API_TOKEN"],
},
timeout=30,
)
if not response.ok:
raise RuntimeError(response.json()["error"]["code"])
print(response.json())<?php
$ch = curl_init('https://api.respawntech.dev/v1/tools/webhooks/layout');
curl_setopt_array($ch, [
CURLOPT_CUSTOMREQUEST => 'GET',
CURLOPT_HTTPHEADER => [
'X-API-Token: ' . getenv('RESPAWN_API_TOKEN'),
],
CURLOPT_RETURNTRANSFER => true,
CURLOPT_TIMEOUT => 30,
]);
$body = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_RESPONSE_CODE);
if ($status >= 400) {
throw new RuntimeException(json_decode($body, true)['error']['code']);
}
print_r(json_decode($body, true));package main
import (
"fmt"
"io"
"net/http"
"os"
"time"
)
func main() {
req, err := http.NewRequest("GET", "https://api.respawntech.dev/v1/tools/webhooks/layout", nil)
if err != nil {
panic(err)
}
req.Header.Set("X-API-Token", os.Getenv("RESPAWN_API_TOKEN"))
client := &http.Client{Timeout: 30 * time.Second}
res, err := client.Do(req)
if err != nil {
panic(err)
}
defer res.Body.Close()
data, err := io.ReadAll(res.Body)
if err != nil {
panic(err)
}
if res.StatusCode >= 400 {
panic(string(data))
}
fmt.Println(string(data))
}Resposta 200
{
"success": true,
"trace_id": "550e8400-e29b-41d4-a716-446655440000",
"data": {
"positions": {
"platform": {
"x": 0,
"y": 0
},
"source:a6d1f8c3-2e9b-4c7a-9f5d-3b0e6a1c8d27": {
"x": 0,
"y": 160
},
"endpoint:8d2f4a61-3c7b-4e9a-b1d5-6f0e2a9c7b34": {
"x": 480,
"y": 160
}
}
}
}Salvar layout do canvas
/v1/tools/webhooks/layout- Custo
- Grátis
- Dry-run
- Não
- Autenticação
- qualquer um de
tools:webhooks:managetools:manage
positions guarda as coordenadas de cada cartão: chaves platform, source:<id> ou endpoint:<id>. Cartões sem posição são arrumados automaticamente pelo dashboard.
curl -sS -X PUT 'https://api.respawntech.dev/v1/tools/webhooks/layout' \
-H "X-API-Token: $RESPAWN_API_TOKEN" \
-H 'Content-Type: application/json' \
-d '{
"positions": {
"source:a6d1f8c3-2e9b-4c7a-9f5d-3b0e6a1c8d27": { "x": 0, "y": 160 },
"endpoint:8d2f4a61-3c7b-4e9a-b1d5-6f0e2a9c7b34": { "x": 520, "y": 200 }
}
}'const response = await fetch('https://api.respawntech.dev/v1/tools/webhooks/layout', {
method: 'PUT',
headers: {
'X-API-Token': process.env.RESPAWN_API_TOKEN,
'Content-Type': 'application/json',
},
body: JSON.stringify({
"positions": {
"source:a6d1f8c3-2e9b-4c7a-9f5d-3b0e6a1c8d27": { "x": 0, "y": 160 },
"endpoint:8d2f4a61-3c7b-4e9a-b1d5-6f0e2a9c7b34": { "x": 520, "y": 200 }
}
}),
});
if (!response.ok) throw new Error((await response.json()).error.code);
console.log(await response.json());import os
import requests
response = requests.put(
"https://api.respawntech.dev/v1/tools/webhooks/layout",
headers={
"X-API-Token": os.environ["RESPAWN_API_TOKEN"],
},
json={
"positions": {
"source:a6d1f8c3-2e9b-4c7a-9f5d-3b0e6a1c8d27": { "x": 0, "y": 160 },
"endpoint:8d2f4a61-3c7b-4e9a-b1d5-6f0e2a9c7b34": { "x": 520, "y": 200 }
}
},
timeout=30,
)
if not response.ok:
raise RuntimeError(response.json()["error"]["code"])
print(response.json())<?php
$ch = curl_init('https://api.respawntech.dev/v1/tools/webhooks/layout');
curl_setopt_array($ch, [
CURLOPT_CUSTOMREQUEST => 'PUT',
CURLOPT_HTTPHEADER => [
'X-API-Token: ' . getenv('RESPAWN_API_TOKEN'),
'Content-Type: application/json',
],
CURLOPT_POSTFIELDS => <<<'JSON'
{
"positions": {
"source:a6d1f8c3-2e9b-4c7a-9f5d-3b0e6a1c8d27": { "x": 0, "y": 160 },
"endpoint:8d2f4a61-3c7b-4e9a-b1d5-6f0e2a9c7b34": { "x": 520, "y": 200 }
}
}
JSON,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_TIMEOUT => 30,
]);
$body = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_RESPONSE_CODE);
if ($status >= 400) {
throw new RuntimeException(json_decode($body, true)['error']['code']);
}
print_r(json_decode($body, true));package main
import (
"fmt"
"io"
"net/http"
"os"
"strings"
"time"
)
func main() {
req, err := http.NewRequest("PUT", "https://api.respawntech.dev/v1/tools/webhooks/layout", strings.NewReader(`{
"positions": {
"source:a6d1f8c3-2e9b-4c7a-9f5d-3b0e6a1c8d27": { "x": 0, "y": 160 },
"endpoint:8d2f4a61-3c7b-4e9a-b1d5-6f0e2a9c7b34": { "x": 520, "y": 200 }
}
}`))
if err != nil {
panic(err)
}
req.Header.Set("X-API-Token", os.Getenv("RESPAWN_API_TOKEN"))
req.Header.Set("Content-Type", "application/json")
client := &http.Client{Timeout: 30 * time.Second}
res, err := client.Do(req)
if err != nil {
panic(err)
}
defer res.Body.Close()
data, err := io.ReadAll(res.Body)
if err != nil {
panic(err)
}
if res.StatusCode >= 400 {
panic(string(data))
}
fmt.Println(string(data))
}Resposta 200 — substitui o layout inteiro do projeto
{
"success": true,
"trace_id": "550e8400-e29b-41d4-a716-446655440000",
"data": {
"positions": {
"source:a6d1f8c3-2e9b-4c7a-9f5d-3b0e6a1c8d27": {
"x": 0,
"y": 160
},
"endpoint:8d2f4a61-3c7b-4e9a-b1d5-6f0e2a9c7b34": {
"x": 520,
"y": 200
}
}
}
}Alertas
O dono da conta recebe notificações no dashboard e por e-mail. Cada tipo pode ser desligado nas preferências de notificação (categoria webhooks).
| Tipo | Quando |
|---|---|
webhook_endpoint_failing | Um endpoint começa a falhar (primeira tentativa com erro depois de estar saudável). Não repete até uma resposta 2xx encerrar a sequência. |
webhook_endpoint_disabled | A desativação automática desliga um endpoint que falha há 5 dias (não quando você o pausa nem por 410 Gone). |
webhook_quota | As entregas do mês atingem 80% e 100% da franquia mensal, uma vez por limiar e por mês. |
Os alertas nunca atrasam nem bloqueiam um envio.
Entrega e retries
O calendário de retries, o que conta como sucesso, Retry-After, 410 Gone e a desativação automática de endpoints que falham por 5 dias seguidos estão no guia Webhooks. Retries automáticos não são cobrados de novo; só Reenviar mensagem e Reenviar entregas com falha geram entregas novas.
API legada da conta
As rotas antigas de webhooks da conta (/v1/account/webhooks) estão obsoletas: respondem com os headers Deprecation: true e Link apontando para os projetos do Webhook Relay. Elas continuam funcionando, e os endpoints criados por elas ficam no projeto padrão. Use o Webhook Relay em integrações novas. A rota do secret dos callbacks de jobs não é obsoleta (veja Secrets e rotação).
Erros
| Código | HTTP | Quando |
|---|---|---|
webhook.relay_disabled | 403 | Webhook Relay ainda não liberado para a conta. |
webhook.limit_reached | 403 | Limite da conta atingido: projetos, endpoints, tipos de evento, fontes ou rotas por fonte. Os limites valem para a conta inteira, somando todos os projetos; ao duplicar um projeto, nada é criado. |
webhook.payload_invalid | 422 | payload não é JSON válido ou não segue o JSON Schema do tipo de evento. Nada é cobrado. |
webhook.invalid_url | 400 | URL de destino inválida ou restrita: só URLs públicas http/https (também vale para a webhook_url do PDF). |
webhook.invalid_event | 400 | Evento desconhecido na API legada de webhooks da conta (/v1/account/webhooks, obsoleta). No Webhook Relay qualquer nome de evento válido é aceito. |
Erros de validação comuns (validation.failed, not_found) e de saldo (billing.insufficient_credits) estão em Erros.
Limites
| Limite | Valor |
|---|---|
| Entregas grátis por mês (por conta) | 1.000 |
Tamanho do payload / corpo recebido | 256 KB |
| Projetos por conta | sem limite |
| Endpoints por conta | 200 |
| Headers customizados por endpoint | 20 |
| Tipos de evento por conta | 100 |
| Mensagens por lote | 100 |
| Entregas por recuperação | 1.000 |
| Fontes por conta | 200 |
| Rotas por fonte | 50 |
| Tipos de evento por rota | 100 |
| Requisições recebidas por fonte | 600 por minuto |
Espera por créditos (blocked_no_credits) | 7 dias |
| Retenção do log de mensagens | 30 dias |
| Janela da busca no corpo das mensagens | 30 dias |
Os limites por conta somam todos os projetos. Os valores são os padrões da plataforma e podem ser ajustados por ela.
Atualizado em
Áudio
Transcrição, voz sintética, efeitos e músicas, análise, conversão, resumo, tradução e revoice de áudio, por REST ou MCP, com jobs assíncronos e preço por minuto.
Realtime
Publique e receba mensagens em tempo real por WebSocket ou SSE, com tokens emitidos no seu servidor, presença, histórico por canal, webhook de canais e reconexão sem perder mensagens.