Arquitete o lado servidor do seu app: modulos, recebedores de webhook, verificacao de assinatura e jobs em background — com exemplos executaveis em Go, Python e Node.
Enquanto o guia "Construindo seu App" cobre o lado cliente (SDKs, embeds, OAuth redirect), este guia cobre o servidor do seu app: o processo que recebe webhooks do Venddor, chama a API com credenciais server-side e mantem o estado da sua integracao. Um backend de app Venddor tipico tem tres responsabilidades principais.
| Responsabilidade | Descricao |
|---|---|
| Recebedor de webhooks | Endpoint HTTP que recebe eventos (pedido criado, produto atualizado, etc.), valida a assinatura e enfileira o processamento. |
| Cliente de API | Camada que chama de volta a API Venddor (REST/GraphQL) usando o access_token do tenant para ler ou escrever dados. |
| Armazenamento de estado | Banco de dados proprio do seu app guardando tokens por tenant, IDs de idempotencia processados e qualquer dado de dominio do seu app. |
Recomendamos organizar o backend do seu app em modulos pequenos e isolados por responsabilidade, ao inves de um unico handler monolitico. Um layout comum (independente da linguagem):
myapp/
webhooks/ # one file per event type: order_created.go, product_updated.go
client/ # thin wrapper around the Venddor REST/GraphQL client
store/ # per-tenant token + idempotency-key persistence
jobs/ # background workers that process queued webhook payloads
server.go # HTTP server wiring: routes, middleware, signature checkEssa separacao permite testar cada modulo (ex.: verificacao de assinatura, parsing de payload, chamada de API) isoladamente, e trocar a fila de background jobs sem tocar no receptor HTTP.
Todo evento e enviado como POST com corpo JSON e o header X-Venddor-Signature (HMAC-SHA256 do corpo bruto usando o client secret do seu app). O handler deve: (1) ler o corpo bruto sem desserializar antes de verificar a assinatura, (2) responder 200 rapido e processar de forma assincrona, (3) usar o ID do evento para idempotencia.
{
"event_id": "evt_9f8c7b6a5d4e",
"type": "order.created",
"tenant_id": "550e8400-e29b-41d4-a716-446655440000",
"created_at": "2026-07-13T14:32:00Z",
"data": {
"id": "ord_abc123",
"order_number": "2026-0042",
"total_cents": 19480,
"status": "O"
}
}Um recebedor de webhook minimo em Go, com verificacao de assinatura HMAC e enfileiramento assincrono do processamento:
package main
import (
"crypto/hmac"
"crypto/sha256"
"encoding/hex"
"encoding/json"
"io"
"log"
"net/http"
"os"
)
type WebhookEvent struct {
EventID string `json:"event_id"`
Type string `json:"type"`
TenantID string `json:"tenant_id"`
CreatedAt string `json:"created_at"`
Data json.RawMessage `json:"data"`
}
var jobs = make(chan WebhookEvent, 256)
func verifySignature(secret string, body []byte, signature string) bool {
mac := hmac.New(sha256.New, []byte(secret))
mac.Write(body)
expected := hex.EncodeToString(mac.Sum(nil))
return hmac.Equal([]byte(expected), []byte(signature))
}
func webhookHandler(w http.ResponseWriter, r *http.Request) {
body, err := io.ReadAll(io.LimitReader(r.Body, 1<<20)) // 1 MiB cap
if err != nil {
http.Error(w, "cannot read body", http.StatusBadRequest)
return
}
defer r.Body.Close()
secret := os.Getenv("VENDDOR_APP_SECRET")
if !verifySignature(secret, body, r.Header.Get("X-Venddor-Signature")) {
http.Error(w, "invalid signature", http.StatusUnauthorized)
return
}
var event WebhookEvent
if err := json.Unmarshal(body, &event); err != nil {
http.Error(w, "invalid payload", http.StatusBadRequest)
return
}
// Respond fast; do the real work in a background worker so Venddor's
// webhook delivery never times out waiting on your business logic.
select {
case jobs <- event:
default:
log.Printf("job queue full, dropping event %s", event.EventID)
}
w.WriteHeader(http.StatusOK)
}
func worker() {
for event := range jobs {
if err := process(event); err != nil {
log.Printf("failed to process %s: %v", event.EventID, err)
}
}
}
func process(event WebhookEvent) error {
switch event.Type {
case "order.created":
return handleOrderCreated(event)
case "product.updated":
return handleProductUpdated(event)
default:
log.Printf("unhandled event type: %s", event.Type)
return nil
}
}
func handleOrderCreated(event WebhookEvent) error {
// Call back into the Venddor API here if you need more order detail,
// using the tenant's stored access_token.
log.Printf("order created for tenant %s: %s", event.TenantID, event.EventID)
return nil
}
func handleProductUpdated(event WebhookEvent) error {
log.Printf("product updated for tenant %s: %s", event.TenantID, event.EventID)
return nil
}
func main() {
go worker()
mux := http.NewServeMux()
mux.HandleFunc("/webhooks/venddor", webhookHandler)
log.Println("listening on :8090")
log.Fatal(http.ListenAndServe(":8090", mux))
}A assinatura e HMAC-SHA256 do corpo bruto da requisicao (bytes exatos, antes de qualquer parsing), usando o client secret do seu app como chave, codificado em hexadecimal. Compare sempre com hmac.Equal / hmac.compare_digest / crypto.timingSafeEqual — nunca com == — para evitar timing attacks.
| Header | Descricao |
|---|---|
| X-Venddor-Signature | HMAC-SHA256 hex do corpo bruto |
| X-Venddor-Event-Id | ID unico do evento, usar para idempotencia |
| X-Venddor-Delivery-Attempt | Numero da tentativa de entrega (1, 2, 3...) |
O mesmo padrao de verificacao — HMAC-SHA256 sobre o corpo bruto, comparacao em tempo constante — em Python (Flask) e Node (Express):
import hashlib
import hmac
import os
from flask import Flask, request, abort
app = Flask(__name__)
APP_SECRET = os.environ["VENDDOR_APP_SECRET"]
def verify_signature(body: bytes, signature: str) -> bool:
expected = hmac.new(APP_SECRET.encode(), body, hashlib.sha256).hexdigest()
return hmac.compare_digest(expected, signature)
@app.post("/webhooks/venddor")
def webhook():
signature = request.headers.get("X-Venddor-Signature", "")
if not verify_signature(request.get_data(), signature):
abort(401)
event = request.get_json()
queue.enqueue("process_event", event) # hand off, respond fast
return "", 200const crypto = require("crypto");
const express = require("express");
const app = express();
app.use(express.raw({ type: "application/json" })); // keep raw body for HMAC
app.post("/webhooks/venddor", (req, res) => {
const expected = crypto
.createHmac("sha256", process.env.VENDDOR_APP_SECRET)
.update(req.body)
.digest("hex");
const signature = req.header("X-Venddor-Signature") || "";
const valid =
signature.length === expected.length &&
crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(signature));
if (!valid) return res.sendStatus(401);
const event = JSON.parse(req.body.toString("utf8"));
jobQueue.add("process-event", event); // hand off, respond fast
res.sendStatus(200);
});Nunca faca trabalho pesado (chamadas de API adicionais, envio de e-mail, processamento de imagem) dentro do handler HTTP do webhook. O Venddor considera a entrega falha se a resposta demorar mais que alguns segundos e vai retentar, o que pode duplicar processamento. Separe: o handler apenas valida e enfileira; um worker separado processa.
Se seu endpoint nao responder 2xx, o Venddor retenta entrega com backoff exponencial (ate 24h). Isso significa que o MESMO evento pode chegar mais de uma vez. Grave o event_id processado (ex.: em uma tabela unica no seu banco) e ignore duplicatas antes de reprocessar.
CREATE TABLE processed_webhook_events (
event_id TEXT PRIMARY KEY,
processed_at TIMESTAMPTZ NOT NULL DEFAULT now()
);
-- Before processing: INSERT ... ON CONFLICT DO NOTHING; skip if 0 rows affected.| Item | Por que |
|---|---|
| HTTPS | Venddor so entrega webhooks para URLs HTTPS validas. |
| Resposta rapida (< 3s) | Handlers lentos disparam retentativas desnecessarias. |
| Fila durable | Nao perder eventos em restart/deploy do seu processo. |
| Secrets fora do codigo | Client secret em variavel de ambiente/secret manager, nunca commitado. |