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

# Payload-Format und Verifizierung

> Wie Social media studio Webhook-Events signiert und zustellt, plus Beispiel-Payloads für gängige Events

Jeder Webhook, den Social media studio zustellt, folgt demselben Umschlag und demselben Signaturverfahren. Diese Seite zeigt den genauen Aufbau der Anfrage, wie du die Signatur verifizierst und wie der `data`-Block bei gängigen Events aussieht.

## Anfrageformat

### Header

Jede Zustellung enthält diese Header:

| Header | Beschreibung |
| - | - |
| `Content-Type` | `application/json` |
| `X-Webhook-Event` | Der Event-Name, z. B. `post.published` |
| `X-Webhook-Delivery-Id` | UUID der Zeile im Zustellungsprotokoll, nützlich zum Deduplizieren von Wiederholungen |
| `X-Webhook-Signature` | HMAC-SHA256 des rohen Request-Bodys, hex-kodiert, signiert mit dem Secret deines Endpunkts |

### Umschlag

Sofern du kein eigenes `payloadTemplate` konfigurierst, ist der Body ein JSON-Objekt mit vier Feldern:

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

* `event` — Der exakte Event-Name (entspricht `X-Webhook-Event`)
* `category` — Die Kategorie, zu der das Event gehört (z. B. `post`, `inbox`, `subscription`)
* `timestamp` — ISO-8601-Zeitstempel des Zeitpunkts, zu dem das Event versendet wurde
* `data` — Die eventspezifische Payload (siehe Beispiele unten)

> [!NOTE]
>
> Wiederholungen verwenden dieselbe `X-Webhook-Delivery-Id`. Nutze sie als Idempotenzschlüssel, damit eine wiederholte Zustellung ein bereits verarbeitetes Event nicht erneut verarbeitet.

## Signature Verification

Jeder Request-Body wird mit dem Secret deines Endpunkts per HMAC-SHA256 signiert. Verifiziere ihn bei jeder Anfrage, um zu bestätigen, dass die Zustellung tatsächlich von Social media studio stammt.

> [!WARNING]
>
> Verifiziere immer mit dem **rohen Request-Body**, bevor er als JSON geparst wird — wenn das geparste Objekt neu serialisiert wird, ändern sich Leerzeichen und die Signatur wird ungültig.

```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)
}
```

## Beispiel-Payloads

Der Aufbau des `data`-Blocks hängt vom Event ab. Hier sind repräsentative Beispiele aus häufig genutzten Kategorien.

### Beitrag

```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"
  }
}
```

### Freigabe

```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"
  }
}
```

### Posteingang

```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"
  }
}
```

### Social-Konto

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

### Abonnement

Das Feld `newPlanName` enthält die interne Tarifkennung, zum Beispiel `scale` für 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"
  }
}
```

### KI

```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/..."
  }
}
```

### Wissensdatenbank

```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
  }
}
```

### Analysen

```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
  }
}
```

### Beste Zeit

```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"
  }
}
```

### Empfehlung

```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]
>
> Die vollständige Liste der 162 Events in 26 Kategorien findest du in der [Webhooks-Übersicht](/de/webhooks/overview.md).

## Eigene Payload-Vorlagen

Erwartet dein Endpunkt eine andere Form (z. B. Slack, Discord), setze ein `payloadTemplate` für dein Webhook-Abonnement. Verfügbare Variablen sind:

* `{{event}}`
* `{{category}}`
* `{{timestamp}}`
* `{{data}}` — das vollständige Datenobjekt als JSON
* `{{data.any_field}}` — jedes verschachtelte Feld der eventspezifischen Payload

Nicht auflösbare Variablen bleiben unverändert stehen (z. B. bleibt `{{data.postId}}` wörtlich erhalten, wenn das Event kein `postId` liefert). Beispiele für Discord- und Slack-Vorlagen findest du auf der Übersichtsseite.

## Lokal testen

Während der Entwicklung:

1. Nutze ein Tunneling-Tool wie [ngrok](https://ngrok.com/) oder [Cloudflare Tunnel](https://developers.cloudflare.com/cloudflare-one/connections/connect-networks/), um deinen lokalen Server verfügbar zu machen.
2. Erstelle unter **Einstellungen → Webhooks** ein Webhook-Abonnement, das auf deine Tunnel-URL zeigt.
3. Klicke beim Abonnement auf die Schaltfläche **Test**, um ein Beispiel-Event zu senden, ohne echte Aktivität auszulösen.
4. Sieh dir die Payload in [Webhook.site](https://webhook.site/) an, wenn du die rohe Anfrage sehen möchtest, bevor du einen Verifizierer schreibst.

> [!WARNING]
>
> Schlägt dein Endpunkt bei **10 aufeinanderfolgenden Zustellungen** fehl, wird das Abonnement automatisch deaktiviert. Aktiviere es nach der Behebung des Problems im Dashboard wieder.


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