Eventos de status
A cada mudança de status, a RaptorX envia um POST para a URL de webhook cadastrada na sua integração,
exatamente como foi cadastrada (inclusive a query string).
Requisição
Seção intitulada “Requisição”| Header | Descrição |
|---|---|
Content-Type |
application/json |
X-App-MerchantId |
Merchant ID da loja |
X-App-Signature |
HMAC-SHA256 do corpo bruto, em hexadecimal minúsculo, com o clientSecret como chave |
X-App-Id |
App ID cadastrado. Só é enviado se foi configurado |
Valide a assinatura
Seção intitulada “Valide a assinatura”Calcule o HMAC-SHA256 do corpo bruto (os bytes recebidos, antes de qualquer parse de JSON) com o seu
clientSecret e compare com X-App-Signature em tempo constante. Rejeite a requisição se não bater.
import { createHmac, timingSafeEqual } from "node:crypto";import express from "express";
const app = express();const clientSecret = process.env.RAPTORX_CLIENT_SECRET;
// guarda o corpo bruto para assinar exatamente o que foi recebidoapp.post("/webhooks/raptorx", express.raw({ type: "application/json" }), (req, res) => { const received = String(req.header("X-App-Signature") ?? ""); const expected = createHmac("sha256", clientSecret).update(req.body).digest("hex");
const a = Buffer.from(received); const b = Buffer.from(expected); if (a.length !== b.length || !timingSafeEqual(a, b)) { return res.sendStatus(401); }
const evento = JSON.parse(req.body.toString("utf8")); // processe de forma idempotente e responda rápido res.sendStatus(200);});import hashlibimport hmacimport os
from flask import Flask, request
app = Flask(__name__)CLIENT_SECRET = os.environ["RAPTORX_CLIENT_SECRET"].encode()
@app.post("/webhooks/raptorx")def webhook(): raw = request.get_data() # corpo bruto received = request.headers.get("X-App-Signature", "") expected = hmac.new(CLIENT_SECRET, raw, hashlib.sha256).hexdigest()
if not hmac.compare_digest(received, expected): return "", 401
evento = request.get_json(force=True) # processe de forma idempotente e responda rápido return "", 200Corpo do evento
Seção intitulada “Corpo do evento”{ "deliveryId": "0b6f9f0e-3c9a-4f0e-9c1e-2f6a8b1d7c55", "orderId": "pedido-84321", "orderDisplayId": "4321", "merchant": { "id": "loja-001", "name": "Pizzaria Exemplo" }, "customerName": "Maria Souza", "event": { "type": "ACCEPTED", "datetime": "2026-10-07T14:10:00.000Z" }, "vehicle": { "type": "MOTORBIKE_BAG", "container": "NORMAL" }, "deliveryPerson": { "id": "5d1c2b7a-...", "name": "Carlos Lima", "phone": "+5519988887777" }, "deliveryPrice": { "price": { "value": 7.5, "currency": "BRL" }, "pricingList": "NORMAL", "additionalPricePercentual": 0 }, "eta": { "updateMethod": "OFFLINE", "pickupEtaInMinutes": 5, "pickupEtaDatetime": "2026-10-07T14:15:00.000Z", "deliveryEtaInMinutes": 25, "deliveryEtaDatetime": "2026-10-07T14:35:00.000Z", "maxDeliveryTime": "2026-10-07T14:35:00.000Z" }}| Campo | Descrição |
|---|---|
deliveryId |
Identificador da entrega na RaptorX |
orderId |
Seu orderId |
orderDisplayId |
Seu orderDisplayId |
merchant.id / merchant.name |
Merchant ID e nome da loja |
customerName |
Nome do destinatário |
event.type |
Tipo do evento (tabela abaixo) |
event.datetime |
Momento do envio do evento |
event.message |
Só em CANCELLED: motivo, até 200 caracteres. É o reason enviado por você ou, em cancelamento feito pela operação, um texto livre |
event.rejectionInfo |
Só em REJECTED: { "reason": "NO_DELIVERYPERSON_AVAILABLE", "metadata": { "description"?: "..." } } |
vehicle |
A partir de ACCEPTED, com entregador atribuído. type: MOTORBIKE_BAG, CAR ou BICYCLE; container: NORMAL |
deliveryPerson |
A partir de ACCEPTED, com entregador atribuído: id, name, phone |
deliveryPrice |
A partir de ACCEPTED. Valor da entrega em BRL; pricingList é NORMAL e additionalPricePercentual é 0 |
eta |
A partir de ACCEPTED. Estimativas, não garantias. pickupEtaInMinutes é um valor fixo de referência (5) |
Os eventos PENDING, REJECTED e CANCELLED não trazem vehicle, deliveryPerson, deliveryPrice nem eta.
Sequência de eventos
Seção intitulada “Sequência de eventos”Uma mudança de status pode gerar dois eventos seguidos, enviados em requisições separadas.
| Quando | Eventos enviados |
|---|---|
| Entrega criada | PENDING |
| Entregador aceitou | ACCEPTED, PICKUP_ONGOING |
| Pedido retirado | ORDER_PICKED, DELIVERY_ONGOING |
| Entregador chegou ao cliente | ARRIVED_AT_CUSTOMER |
| Entrega concluída | ORDER_DELIVERED, DELIVERY_FINISHED |
| Entrega cancelada (por você ou pela operação) | CANCELLED |
Nenhum entregador aceitou até rejectAfter |
REJECTED |
| Devolução iniciada | RETURNING_TO_MERCHANT |
| Devolvido à loja | RETURNED_TO_MERCHANT |
Respostas, tentativas e prazos
Seção intitulada “Respostas, tentativas e prazos”- Responda com qualquer status 2xx, em até 10 segundos. Faça o processamento pesado depois de responder.
- Resposta fora de 2xx, timeout ou erro de rede contam como falha.
- Redirecionamentos (3xx) não são seguidos e contam como falha: cadastre a URL final.
- Em caso de falha, a RaptorX tenta de novo: até 4 rodadas, com intervalos de 1, 5 e 30 minutos entre elas. Em cada rodada, falhas 5xx, 429 e de rede ainda são reenviadas algumas vezes em poucos segundos.
- Depois disso, o evento não é reenviado automaticamente.
- A entrega é pelo menos uma vez: o mesmo evento pode chegar duplicado, e eventos de uma rodada anterior podem chegar fora de ordem. Use
deliveryIdeevent.typepara tratar de forma idempotente e não retroceda um status já mais avançado. event.datetimeé o momento do envio e não do acontecimento.- A URL de webhook precisa ser HTTPS e pública.

