Respawn Tech Dev

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 secret pró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.

OperaçãoRota
Listar projetosGET/projects
Criar projetoPOST/projects
Consultar projetoGET/projects/{id}
Alterar projetoPATCH/projects/{id}
Excluir projetoDELETE/projects/{id}
Duplicar projetoPOST/projects/{id}/duplicate
Consultar uso do mêsGET/usage
Criar endpointPOST/endpoints
Listar endpointsGET/endpoints
Consultar endpointGET/endpoints/{id}
Alterar endpointPATCH/endpoints/{id}
Excluir endpointDELETE/endpoints/{id}
Revelar secretGET/endpoints/{id}/secret
Trocar secretPOST/endpoints/{id}/rotate-secret
Enviar ping de testePOST/endpoints/{id}/test
Reenviar entregas com falhaPOST/endpoints/{id}/recover
Enviar mensagemPOST/messages
Enviar mensagens em lotePOST/messages/batch
Listar mensagensGET/messages
Consultar mensagemGET/messages/{id}
Reenviar mensagemPOST/messages/{id}/retry
Declarar tipo de eventoPOST/event-types
Listar tipos de eventoGET/event-types
Excluir tipo de eventoDELETE/event-types/{id}
Criar fonte de ingestãoPOST/sources
Listar fontesGET/sources
Consultar fonteGET/sources/{id}
Alterar fontePATCH/sources/{id}
Excluir fonteDELETE/sources/{id}
Trocar URL de ingestãoPOST/sources/{id}/rotate-token
Definir secret do provedorPUT/sources/{id}/verification-secret
Consultar último evento recebidoGET/sources/{id}/sample
Enviar evento de teste pela fontePOST/sources/{id}/test
Criar rotaPOST/routes
Listar rotasGET/routes
Alterar rotaPATCH/routes/{id}
Excluir rotaDELETE/routes/{id}
Consultar mapa do fluxoGET/graph
Consultar layout do canvasGET/layout
Salvar layout do canvasPUT/layout
Receber evento de um provedorPOST/in/{token}

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.

ItemCréditosNotas
Entrega além da franquia mensal0,002mensagens da API, ingestão, retry manual e recuperação
Eventos da plataforma e callbacks de jobs0,0ex.: 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`.

ItemGrátis por mês
Entregas1.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

GET/v1/tools/webhooks/usage
Custo
Grátis
Dry-run
Não
Autenticação
qualquer um detools: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, chamado Default), 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, /routes e /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

GET/v1/tools/webhooks/projects
Custo
Grátis
Dry-run
Não
Autenticação
qualquer um detools: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

POST/v1/tools/webhooks/projects
Custo
Grátis
Dry-run
Não
Autenticação
qualquer um detools: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

GET/v1/tools/webhooks/projects/{id}
Custo
Grátis
Dry-run
Não
Autenticação
qualquer um detools: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

PATCH/v1/tools/webhooks/projects/{id}
Custo
Grátis
Dry-run
Não
Autenticação
qualquer um detools: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

DELETE/v1/tools/webhooks/projects/{id}
Custo
Grátis
Dry-run
Não
Autenticação
qualquer um detools: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

POST/v1/tools/webhooks/projects/{id}/duplicate
Custo
Grátis
Dry-run
Não
Autenticação
qualquer um detools: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_url novas (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

POST/v1/tools/webhooks/endpoints
Custo
Grátis
Dry-run
Não
Autenticação
qualquer um detools:webhooks:managetools:manage
  • url (obrigatório): URL pública http ou https. 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, discord ou teams. 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-Agent e Host não podem ser sobrescritos.
  • rate_limit_per_sec: teto de envios por segundo para este endpoint (0 = sem teto próprio).
  • O secret vem 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

GET/v1/tools/webhooks/endpoints
Custo
Grátis
Dry-run
Não
Autenticação
qualquer um detools: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

GET/v1/tools/webhooks/endpoints/{id}
Custo
Grátis
Dry-run
Não
Autenticação
qualquer um detools: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

PATCH/v1/tools/webhooks/endpoints/{id}
Custo
Grátis
Dry-run
Não
Autenticação
qualquer um detools: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

DELETE/v1/tools/webhooks/endpoints/{id}
Custo
Grátis
Dry-run
Não
Autenticação
qualquer um detools: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

GET/v1/tools/webhooks/endpoints/{id}/secret
Custo
Grátis
Dry-run
Não
Autenticação
qualquer um detools: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

POST/v1/tools/webhooks/endpoints/{id}/rotate-secret
Custo
Grátis
Dry-run
Não
Autenticação
qualquer um detools: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

POST/v1/tools/webhooks/endpoints/{id}/test
Custo
Grátis
Dry-run
Não
Autenticação
qualquer um detools: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

POST/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 detools: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), com Content-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_headers nã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

POST/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 detools: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 header X-Idempotency-Key): repetir a chave devolve a primeira mensagem com duplicate: true e 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 mostra deliveries, free_deliveries e charged_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

POST/v1/tools/webhooks/messages/batch
Custo
Por entrega, cada mensagem cobrada separadamente
Dry-run
Suportado (X-Dry-Run: true)
Autenticação
qualquer um detools: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

GET/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 detools: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_type e endpoint_id: filtros exatos.
  • failed=true: só mensagens com ao menos uma entrega failed, exhausted ou blocked_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

GET/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 detools: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 entregaSignificado
pendingAguardando a próxima tentativa (next_attempt_at).
sendingTentativa em andamento.
succeededO endpoint respondeu 2xx.
exhaustedTodas as tentativas falharam. Pode ser reenviada.
failedParou de vez: endpoint respondeu 410 Gone, foi desativado ou a URL ficou inválida. Pode ser reenviada.
canceledCancelada, por exemplo porque o endpoint foi excluído com a entrega pendente.
blocked_no_creditsMensagem 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

POST/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 detools: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

POST/v1/tools/webhooks/event-types
Custo
Grátis
Dry-run
Não
Autenticação
qualquer um detools: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

GET/v1/tools/webhooks/event-types
Custo
Grátis
Dry-run
Não
Autenticação
qualquer um detools: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

DELETE/v1/tools/webhooks/event-types/{id}
Custo
Grátis
Dry-run
Não
Autenticação
qualquer um detools: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.

providerComo a requisição é verificada
stripeHeader Stripe-Signature com o signing secret do Stripe (whsec_…), tolerância de 5 minutos.
githubHeader X-Hub-Signature-256 com o secret do webhook do GitHub.
shopifyHeader X-Shopify-Hmac-Sha256 com o secret do app Shopify.
hmac_genericHeader X-Webhook-Signature: sha256=<hex> (HMAC-SHA256 do corpo).
standard_webhooksHeaders webhook-id, webhook-timestamp e webhook-signature.
basic_authHeader Authorization: Basic …; o secret é usuario:senha.
noneSem 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

POST/v1/tools/webhooks/sources
Custo
Grátis
Dry-run
Não
Autenticação
qualquer um detools:webhooks:managetools:manage
  • name e provider (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 a ingest_url: enquanto ele não for definido em Definir secret do provedor, a fonte responde 401 a toda requisição (exceto com provider: "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 responde 200 sem 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, Authorization e headers de proxy nunca são repassados.
  • Cadastre a ingest_url da 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

GET/v1/tools/webhooks/sources
Custo
Grátis
Dry-run
Não
Autenticação
qualquer um detools: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

GET/v1/tools/webhooks/sources/{id}
Custo
Grátis
Dry-run
Não
Autenticação
qualquer um detools: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

PATCH/v1/tools/webhooks/sources/{id}
Custo
Grátis
Dry-run
Não
Autenticação
qualquer um detools: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

DELETE/v1/tools/webhooks/sources/{id}
Custo
Grátis
Dry-run
Não
Autenticação
qualquer um detools: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

POST/v1/tools/webhooks/sources/{id}/rotate-token
Custo
Grátis
Dry-run
Não
Autenticação
qualquer um detools: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

PUT/v1/tools/webhooks/sources/{id}/verification-secret
Custo
Grátis
Dry-run
Não
Autenticação
qualquer um detools: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

GET/v1/tools/webhooks/sources/{id}/sample
Custo
Grátis
Dry-run
Não
Autenticação
qualquer um detools: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

POST/v1/tools/webhooks/sources/{id}/test
Custo
Grátis
Dry-run
Não
Autenticação
qualquer um detools: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

POST/v1/tools/webhooks/routes
Custo
Grátis
Dry-run
Não
Autenticação
qualquer um detools: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

GET/v1/tools/webhooks/routes
Custo
Grátis
Dry-run
Não
Autenticação
qualquer um detools: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

PATCH/v1/tools/webhooks/routes/{id}
Custo
Grátis
Dry-run
Não
Autenticação
qualquer um detools: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

DELETE/v1/tools/webhooks/routes/{id}
Custo
Grátis
Dry-run
Não
Autenticação
qualquer um detools: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

POST/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:

StatusQuando
200Aceito, duplicado (reenvio do provedor) ou sem rota para o tipo.
401Assinatura inválida, ou secret do provedor ainda não definido; nada é gravado.
404Fonte desconhecida ou desativada, ou ferramenta não liberada na conta.
429Mais de 600 requisições por minuto para a fonte.
5xxFalha 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

GET/v1/tools/webhooks/graph
Custo
Grátis
Dry-run
Não
Autenticação
qualquer um detools: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

GET/v1/tools/webhooks/layout
Custo
Grátis
Dry-run
Não
Autenticação
qualquer um detools: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

PUT/v1/tools/webhooks/layout
Custo
Grátis
Dry-run
Não
Autenticação
qualquer um detools: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).

TipoQuando
webhook_endpoint_failingUm 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_disabledA desativação automática desliga um endpoint que falha há 5 dias (não quando você o pausa nem por 410 Gone).
webhook_quotaAs 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ódigoHTTPQuando
webhook.relay_disabled403Webhook Relay ainda não liberado para a conta.
webhook.limit_reached403Limite 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_invalid422payload não é JSON válido ou não segue o JSON Schema do tipo de evento. Nada é cobrado.
webhook.invalid_url400URL de destino inválida ou restrita: só URLs públicas http/https (também vale para a webhook_url do PDF).
webhook.invalid_event400Evento 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

LimiteValor
Entregas grátis por mês (por conta)1.000
Tamanho do payload / corpo recebido256 KB
Projetos por contasem limite
Endpoints por conta200
Headers customizados por endpoint20
Tipos de evento por conta100
Mensagens por lote100
Entregas por recuperação1.000
Fontes por conta200
Rotas por fonte50
Tipos de evento por rota100
Requisições recebidas por fonte600 por minuto
Espera por créditos (blocked_no_credits)7 dias
Retenção do log de mensagens30 dias
Janela da busca no corpo das mensagens30 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