> ## Documentation Index
> Fetch the complete documentation index at: https://docs.so-me.studio/llms.txt
> Use this file to discover all available pages before exploring further.

# Formato do payload e verificação

> Como o Social media studio assina e entrega eventos de webhook, com exemplos de payload dos eventos mais comuns

Todo webhook entregue pelo Social media studio segue o mesmo envelope e o mesmo esquema de assinatura. Esta página mostra o formato exato da requisição, como verificar a assinatura e como é o bloco `data` dos eventos mais comuns.

## Formato da requisição

### Cabeçalhos

Toda entrega inclui estes cabeçalhos:

| Cabeçalho | Descrição |
| - | - |
| `Content-Type` | `application/json` |
| `X-Webhook-Event` | O nome do evento, por exemplo `post.published` |
| `X-Webhook-Delivery-Id` | UUID da linha do log de entrega, útil para deduplicar novas tentativas |
| `X-Webhook-Signature` | HMAC-SHA256 do corpo bruto da requisição, em hexadecimal, assinado com o segredo do seu endpoint |

### Envelope

A menos que você configure um `payloadTemplate` personalizado, o corpo é um objeto JSON com quatro campos:

```json theme={null}
{
  "event": "post.published",
  "category": "post",
  "timestamp": "2026-04-22T14:30:00.000Z",
  "data": {
    "postId": "01J...",
    "platform": "instagram",
    "userId": "usr_..."
  }
}
```

* `event`: o nome exato do evento (igual a `X-Webhook-Event`)
* `category`: a categoria à qual o evento pertence (por exemplo, `post`, `inbox`, `subscription`)
* `timestamp`: timestamp ISO-8601 de quando o evento foi despachado
* `data`: o payload específico do evento (veja os exemplos abaixo)

> [!NOTE]
>
> As novas tentativas reutilizam o mesmo `X-Webhook-Delivery-Id`. Use-o como chave de idempotência para que uma entrega repetida não reprocesse um evento já tratado.

## Signature verification

Todo corpo de requisição é assinado com o segredo do seu endpoint usando HMAC-SHA256. Verifique-o em cada requisição para confirmar que a entrega realmente veio do Social media studio.

> [!WARNING]
>
> Verifique sempre usando o **corpo bruto da requisição**, antes de ele ser interpretado como JSON; reserializar o objeto interpretado altera os espaços em branco e invalida a assinatura.

```javascript Node.js / Express theme={null}
import express from "express";
import crypto from "crypto";

const app = express();
const WEBHOOK_SECRET = process.env.SOMESTUDIO_WEBHOOK_SECRET;

app.post(
  "/webhook",
  express.raw({ type: "application/json" }),
  (req, res) => {
    const signature = req.header("X-Webhook-Signature");
    const expected = crypto
      .createHmac("sha256", WEBHOOK_SECRET)
      .update(req.body)
      .digest("hex");

    if (
      !signature ||
      !crypto.timingSafeEqual(
        Buffer.from(signature, "hex"),
        Buffer.from(expected, "hex"),
      )
    ) {
      return res.status(401).send("Invalid signature");
    }

    const payload = JSON.parse(req.body.toString("utf8"));
    console.log(payload.event, payload.data);

    res.status(200).send("ok");
  },
);

app.listen(3000);
```

```python Python / Flask theme={null}
import hmac
import hashlib
import os
from flask import Flask, request, abort

app = Flask(__name__)
WEBHOOK_SECRET = os.environ["SOMESTUDIO_WEBHOOK_SECRET"]

@app.post("/webhook")
def webhook():
    signature = request.headers.get("X-Webhook-Signature", "")
    expected = hmac.new(
        WEBHOOK_SECRET.encode(),
        request.get_data(),
        hashlib.sha256,
    ).hexdigest()

    if not hmac.compare_digest(signature, expected):
        abort(401)

    payload = request.get_json()
    print(payload["event"], payload["data"])
    return "ok", 200
```

```php PHP theme={null}
<?php
$secret    = getenv('SOMESTUDIO_WEBHOOK_SECRET');
$signature = $_SERVER['HTTP_X_WEBHOOK_SIGNATURE'] ?? '';
$body      = file_get_contents('php://input');
$expected  = hash_hmac('sha256', $body, $secret);

if (!hash_equals($expected, $signature)) {
    http_response_code(401);
    exit('Invalid signature');
}

$payload = json_decode($body, true);
error_log($payload['event']);

http_response_code(200);
echo 'ok';
```

```go Go theme={null}
package main

import (
    "crypto/hmac"
    "crypto/sha256"
    "encoding/hex"
    "io"
    "net/http"
    "os"
)

var secret = []byte(os.Getenv("SOMESTUDIO_WEBHOOK_SECRET"))

func webhook(w http.ResponseWriter, r *http.Request) {
    body, _ := io.ReadAll(r.Body)
    defer r.Body.Close()

    mac := hmac.New(sha256.New, secret)
    mac.Write(body)
    expected := hex.EncodeToString(mac.Sum(nil))

    if !hmac.Equal([]byte(expected), []byte(r.Header.Get("X-Webhook-Signature"))) {
        http.Error(w, "Invalid signature", http.StatusUnauthorized)
        return
    }

    w.WriteHeader(http.StatusOK)
    w.Write([]byte("ok"))
}

func main() {
    http.HandleFunc("/webhook", webhook)
    http.ListenAndServe(":3000", nil)
}
```

## Exemplos de payload

O formato do bloco `data` depende do evento. Aqui estão exemplos representativos das categorias mais usadas.

### Post

```json theme={null}
{
  "event": "post.published",
  "category": "post",
  "timestamp": "2026-04-22T14:30:00.000Z",
  "data": {
    "postId": "01JABC123...",
    "platform": "instagram",
    "userId": "usr_abc123"
  }
}
```

```json theme={null}
{
  "event": "post.failed",
  "category": "post",
  "timestamp": "2026-04-22T14:31:00.000Z",
  "data": {
    "postId": "01JABC123...",
    "platform": "twitter",
    "failureReason": "Social account session has expired"
  }
}
```

### Aprovação

```json theme={null}
{
  "event": "post.approval.approved",
  "category": "approval",
  "timestamp": "2026-04-22T14:32:00.000Z",
  "data": {
    "postId": "01JABC123...",
    "userId": "usr_abc123",
    "reviewerId": "mem_xyz789"
  }
}
```

### Caixa de entrada

```json theme={null}
{
  "event": "inbox.message_received",
  "category": "inbox",
  "timestamp": "2026-04-22T14:33:00.000Z",
  "data": {
    "threadId": "thr_abc",
    "messageId": "msg_xyz",
    "platform": "instagram",
    "fromUser": { "id": "ig_456", "name": "Jane Doe" }
  }
}
```

### WhatsApp

```json theme={null}
{
  "event": "whatsapp.message_sent",
  "category": "whatsapp",
  "timestamp": "2026-04-22T14:33:30.000Z",
  "data": {
    "accountId": "15551234567",
    "to": "+15557654321",
    "templateName": "order_update",
    "messageId": "wamid.HBgM..."
  }
}
```

```json theme={null}
{
  "event": "whatsapp.template_created",
  "category": "whatsapp",
  "timestamp": "2026-04-22T14:33:45.000Z",
  "data": {
    "accountId": "15551234567",
    "name": "order_update",
    "language": "en_US",
    "category": "UTILITY"
  }
}
```

### Conta social

```json theme={null}
{
  "event": "account.token_expired",
  "category": "account",
  "timestamp": "2026-04-22T14:34:00.000Z",
  "data": {
    "accountId": "acc_123",
    "platform": "facebook",
    "postId": "01JABC123..."
  }
}
```

### Assinatura

O campo `newPlanName` contém o identificador interno do plano, como `scale` para o Advanced.

```json theme={null}
{
  "event": "subscription.upgraded",
  "category": "subscription",
  "timestamp": "2026-04-22T14:35:00.000Z",
  "data": {
    "subscriptionId": "lsq_sub_123",
    "newPlanId": "pln_scale",
    "newPlanName": "scale"
  }
}
```

### IA

```json theme={null}
{
  "event": "ai.image_generated",
  "category": "ai",
  "timestamp": "2026-04-22T14:36:00.000Z",
  "data": {
    "imageId": "img_abc",
    "userId": "usr_xyz",
    "prompt": "A sunset over the mountains",
    "url": "https://cdn.so-me.studio/..."
  }
}
```

### Central de conhecimento

```json theme={null}
{
  "event": "knowledge.created",
  "category": "knowledge",
  "timestamp": "2026-04-22T14:36:30.000Z",
  "data": {
    "id": "kn_abc123",
    "title": "Brand voice guide",
    "sourceType": "pdf",
    "isActive": true
  }
}
```

### Análises

```json theme={null}
{
  "event": "analytics.report_generated",
  "category": "analytics",
  "timestamp": "2026-04-22T14:00:00.000Z",
  "data": {
    "generatedAt": "2026-04-22T14:00:00.000Z",
    "accountCount": 4,
    "windowHours": 2
  }
}
```

### Melhor horário

```json theme={null}
{
  "event": "best_time.recommendation_recomputed",
  "category": "best_time",
  "timestamp": "2026-04-22T03:00:00.000Z",
  "data": {
    "socialAccountId": "acc_123",
    "platform": "instagram",
    "isPersonalized": true,
    "postCount": 87,
    "timezone": "America/New_York"
  }
}
```

### Indicação

```json theme={null}
{
  "event": "referral.reward_earned",
  "category": "referral",
  "timestamp": "2026-04-22T14:37:00.000Z",
  "data": {
    "referralId": "ref_abc",
    "userId": "usr_xyz",
    "role": "referrer",
    "creditsAwarded": 10
  }
}
```

> [!NOTE]
>
> Veja a [Visão geral de Webhooks](/pt-BR/webhooks/overview.md) para a lista completa de 162 eventos em 26 categorias.

## Templates de payload personalizados

Se o seu endpoint espera um formato diferente (por exemplo, Slack ou Discord), defina um `payloadTemplate` na sua assinatura de webhook. As variáveis disponíveis são:

* `{{event}}`
* `{{category}}`
* `{{timestamp}}`
* `{{data}}`: o objeto de dados completo em JSON
* `{{data.any_field}}`: qualquer campo aninhado do payload específico do evento

As variáveis não resolvidas são mantidas como estão (por exemplo, `{{data.postId}}` permanece literal se o evento não fornecer `postId`). Veja a página de visão geral para exemplos de template para Discord e Slack.

## Testes locais

Durante o desenvolvimento:

1. Use uma ferramenta de tunelamento como o [ngrok](https://ngrok.com/) ou o [Cloudflare Tunnel](https://developers.cloudflare.com/cloudflare-one/connections/connect-networks/) para expor o seu servidor local.
2. Crie uma assinatura de webhook em **Configurações → Webhooks** apontando para a URL do seu túnel.
3. Clique no botão **Testar** da assinatura para enviar um evento de exemplo sem disparar atividade real.
4. Inspecione o payload no [Webhook.site](https://webhook.site/) se quiser ver a requisição bruta antes de escrever um verificador.

> [!WARNING]
>
> Se o seu endpoint falhar em **10 entregas consecutivas**, a assinatura é desativada automaticamente. Reative-a pelo painel depois de corrigir o problema.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.