V
Venddor-IO
RecursosComo FuncionaPlanosDesenvolvedoresDocsLoginComece Gratis
Guia do Desenvolvedor

Arquitetura de Backend

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.

Guia do Desenvolvedor

Primeiros PassosIntegração via APIConstruindo seu AppArquitetura de BackendModelos de DadosReferencia APITestando seu AppPublicacao no Marketplace

Nesta pagina

Arquitetura de BackendOrganizando ModulosRecebedor de WebhooksExemplo em GoVerificacao de AssinaturaExemplos Python e NodeJobs em BackgroundRetentativas e IdempotenciaChecklist de Deploy
Primeiros PassosIntegração via APIConstruindo seu AppArquitetura de BackendModelos de DadosReferencia APITestando seu AppPublicacao no Marketplace
DocsGuia do DesenvolvedorArquitetura de Backend

Arquitetura de Backend

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.

ResponsabilidadeDescricao
Recebedor de webhooksEndpoint HTTP que recebe eventos (pedido criado, produto atualizado, etc.), valida a assinatura e enfileira o processamento.
Cliente de APICamada que chama de volta a API Venddor (REST/GraphQL) usando o access_token do tenant para ler ou escrever dados.
Armazenamento de estadoBanco de dados proprio do seu app guardando tokens por tenant, IDs de idempotencia processados e qualquer dado de dominio do seu app.
O backend do seu app roda na SUA infraestrutura (nao dentro do Venddor). O Venddor apenas chama HTTPS webhooks para a URL que voce registrou e aceita chamadas de API autenticadas com o token do seu app.

Organizando Modulos

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):

text
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 check

Essa 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.

Recebedor de Webhooks

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.

json
{
  "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"
  }
}

Exemplo em Go

Um recebedor de webhook minimo em Go, com verificacao de assinatura HMAC e enfileiramento assincrono do processamento:

go
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))
}
Em producao, troque o channel em memoria por uma fila durable (SQS, Redis, NATS, Postgres com SELECT FOR UPDATE SKIP LOCKED) para nao perder eventos em caso de restart do processo.

Verificacao de Assinatura

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.

HeaderDescricao
X-Venddor-SignatureHMAC-SHA256 hex do corpo bruto
X-Venddor-Event-IdID unico do evento, usar para idempotencia
X-Venddor-Delivery-AttemptNumero da tentativa de entrega (1, 2, 3...)

Exemplos Python e Node

O mesmo padrao de verificacao — HMAC-SHA256 sobre o corpo bruto, comparacao em tempo constante — em Python (Flask) e Node (Express):

python
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 "", 200
javascript
const 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);
});

Jobs em Background

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.

Retentativas e Idempotencia

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.

sql
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.

Checklist de Deploy

ItemPor que
HTTPSVenddor so entrega webhooks para URLs HTTPS validas.
Resposta rapida (< 3s)Handlers lentos disparam retentativas desnecessarias.
Fila durableNao perder eventos em restart/deploy do seu processo.
Secrets fora do codigoClient secret em variavel de ambiente/secret manager, nunca commitado.
Anterior
Construindo seu App
Proximo
Modelos de Dados