Entwickler, Bot-API & Webhooks
Automatisiere deine Community mit Bots, oder schicke Nachrichten per eingehendem Webhook. Die SpeakSpeak-API ist Discord-kompatibel: verbreitete Bot-Bibliotheken lassen sich mit wenigen Anpassungen portieren.
Erste Schritte
- Anwendung erstellen. In den Entwickler-Einstellungen deines SpeakSpeak-Kontos legst du eine Anwendung an. Zu jeder Anwendung gehört automatisch ein Bot-Benutzer.
- Bot-Token erzeugen. Die Anwendung stellt ein Token der Form
ssbot_<app>.<secret>aus. Das Token wird nur einmal angezeigt, bewahre es sicher auf, es ist so mächtig wie ein Passwort. - Bot installieren. Installiere die Anwendung auf einem Server, auf dem du Server verwalten darfst, und wähle die Berechtigungen. Erst danach kann der Bot dort handeln.
Danach spricht dein Bot direkt mit der API:
# Wer bin ich?, bestätigt, dass das Token gültig ist
curl https://api.speakspeak.net/api/v1/users/@me \
-H "Authorization: Bot ssbot_AZ-wUWG0….ovB1whFr…"
Authentifizierung
Jede REST-Anfrage trägt den Authorization-Header mit dem Präfix Bot:
Authorization: Bot ssbot_<app>.<secret>
- Das Token ist eine Obergrenze, keine Verleihung. Was ein Bot tatsächlich darf, ist immer Token-Rechte ∩ Rolle des Bots auf dem Server, auf dem Zielserver bei jedem Aufruf neu geprüft. Ein Token kann nie mehr, als die Bot-Rolle erlaubt.
- Pro Server installiert. Ein Bot handelt nur auf Servern, auf denen die Anwendung installiert ist.
- Widerrufbar. Ein geleaktes Token widerrufst du jederzeit in den Entwickler-Einstellungen; Aufrufe damit scheitern sofort.
REST-API
Discord-kompatible Endpunkte unter https://api.speakspeak.net/api/v1. IDs sind UUIDs (statt Discord-Snowflakes), sonst folgen Pfade und JSON-Formen dem gewohnten Schema.
| Bereich | Beispiele |
|---|---|
| Identität | GET /users/@me, GET /users/{id} |
| Server | GET /guilds/{id}, GET /guilds/{id}/members, GET /guilds/{id}/audit-logs |
| Kanäle | GET|PATCH|DELETE /channels/{id}, POST /guilds/{id}/channels |
| Nachrichten | GET|POST /channels/{id}/messages, GET|PATCH|DELETE /channels/{id}/messages/{mid} |
| Reaktionen | PUT|DELETE /channels/{id}/messages/{mid}/reactions/{emoji}/@me |
| Pins | GET /channels/{id}/pins, PUT|DELETE /channels/{id}/pins/{mid} |
| Rollen | GET|POST /guilds/{id}/roles, PATCH|DELETE /guilds/{id}/roles/{rid}, PUT|DELETE /guilds/{id}/members/{uid}/roles/{rid} |
| Mitglieder | PATCH /guilds/{id}/members/{uid} (Nick/Rollen), DELETE … (Kick) |
| Bans | GET /guilds/{id}/bans, GET|PUT|DELETE /guilds/{id}/bans/{uid} |
| Einladungen | POST /channels/{id}/invites, DELETE /invites/{code} |
| Tippen | POST /channels/{id}/typing |
Eine Nachricht senden, und die Antwort:
curl -X POST https://api.speakspeak.net/api/v1/channels/{channel_id}/messages \
-H "Authorization: Bot ssbot_…" \
-H "Content-Type: application/json" \
-d '{"content": "Hallo aus meinem Bot 👋"}'
{
"id": "019fb0a1-2c3d-7e4f-…",
"channel_id": "019fb0…",
"content": "Hallo aus meinem Bot 👋",
"author": { "id": "019fb0…", "username": "meinbot#bot", "bot": true },
"timestamp": "2026-07-30T13:12:42.000Z"
}
Grenzen wie bei Discord: content max. 2000 Zeichen, Rollennamen max. 100, Kanal-Thema max. 1024. Über der Grenze, oder ein Null-Byte im Text, gibt es ein sauberes 400.
Eingehende Webhooks
Ein Webhook postet in genau einen Kanal, ohne Bot und ohne Login, die Zugangsdaten stecken in der URL. Ideal für CI-, Monitoring- oder Deploy-Meldungen.
- Erstellen: in den Kanal-Einstellungen (oder per API). Du bekommst
webhook_idundtoken, das Token nur einmal. - Auslösen: ein einfacher
POSTauf die Webhook-URL.
curl -X POST "https://api.speakspeak.net/api/v1/webhooks/{webhook_id}/{token}?wait=true" \
-H "Content-Type: application/json" \
-d '{"content": "✅ Build 2016 deployed", "username": "CI"}'
?wait=true lässt den Server die erstellte Nachricht zurückgeben (praktisch zum Bestätigen). Wird der Kanal überlastet, antwortet der Webhook mit 429 und einem Retry-After-Header, dann kurz warten und erneut senden.
Gateway (Echtzeit-Events)
Für Live-Events (neue Nachrichten, Reaktionen, Beitritte) hält dein Bot eine WebSocket-Verbindung zum Gateway. Ablauf wie bei Discord: HELLO (op 10) → IDENTIFY (op 2) → READY, danach regelmäßige Heartbeats.
// Node.js, minimaler Handshake (ohne Bibliothek)
const ws = new WebSocket("wss://api.speakspeak.net/api/v1/gateway");
ws.onmessage = (ev) => {
const m = JSON.parse(ev.data);
if (m.op === 10) { // HELLO
setInterval(() => ws.send(JSON.stringify({ op: 1, d: null })),
m.d.heartbeat_interval); // Heartbeat
ws.send(JSON.stringify({ op: 2, d: { // IDENTIFY
token: "ssbot_…", intents: 33281, // GUILDS | GUILD_MESSAGES | MESSAGE_CONTENT
properties: { os: "linux", browser: "meinbot", device: "meinbot" }
}}));
} else if (m.t === "MESSAGE_CREATE") {
console.log("neue Nachricht:", m.d.content);
}
};
Welche Events du bekommst, steuern die Intents (Bitfeld im IDENTIFY), genau wie bei Discord. Das Gateway kennt diese Bits:
| Intent | Bit | Wert | Schaltet frei |
|---|---|---|---|
GUILDS | 1 << 0 | 1 | Kanal-, Thread-, Rollen- und Server-Metadaten-Events |
GUILD_MEMBERS | 1 << 1 | 2 | GUILD_MEMBER_ADD/REMOVE/UPDATE |
GUILD_VOICE_STATES | 1 << 7 | 128 | VOICE_STATE_UPDATE |
GUILD_PRESENCES | 1 << 8 | 256 | wird akzeptiert, liefert aber keine Events (siehe „Nur schreibend“ oben) |
GUILD_MESSAGES | 1 << 9 | 512 | MESSAGE_CREATE/UPDATE/DELETE |
GUILD_MESSAGE_REACTIONS | 1 << 10 | 1024 | MESSAGE_REACTION_ADD/REMOVE |
GUILD_MESSAGE_TYPING | 1 << 11 | 2048 | TYPING_START |
MESSAGE_CONTENT | 1 << 15 | 32768 | den Inhalt von Nachrichten-Events, nicht die Events selbst (siehe unten) |
Unbekannte Bits werden ignoriert. intents: 0 ist gültig, liefert aber null Events, ein Bot, der auf Nachrichten reagieren soll, braucht mindestens GUILD_MESSAGES.
MESSAGE_CONTENT: Event kommt an, Inhalt ist leer?
MESSAGE_CONTENT (1 << 15) entscheidet nicht, ob Nachrichten-Events ankommen, das tut GUILD_MESSAGES (1 << 9). MESSAGE_CONTENT entscheidet nur, ob die Inhaltsfelder gefüllt sind: Ohne das Bit kommt MESSAGE_CREATE trotzdem an, aber content ist leer und embeds und attachments sind leere Listen, exakt wie bei Discords privilegiertem Intent. Das Symptom: dein Handler feuert, m.d.content ist "". Die Lösung: MESSAGE_CONTENT zusätzlich zu GUILD_MESSAGES setzen, zusammen mit GUILDS ergibt das intents: 33281 wie im Beispiel oben. Anders als bei Discord gibt es keinen Freigabeprozess, Bit setzen genügt; was der Bot überhaupt sieht, begrenzen weiterhin seine Kanal-Berechtigungen.
Status deines Bots (op 3)
Mit PRESENCE_UPDATE (op 3) setzt du, wie dein Bot in der Mitgliederliste erscheint, in allen Servern gleichzeitig. Du kannst denselben Aufbau auch direkt im IDENTIFY unter presence mitschicken; ohne Angabe ist dein Bot ab READY online.
ws.send(JSON.stringify({ op: 3, d: {
status: "dnd", // online | idle | dnd | invisible | offline
activities: [{ type: 0, name: "Celeste" }] // → "Playing Celeste"
}}));
- Nur
activities[0]wird ausgewertet: Typ0→ „Playing X“,1→ „Streaming X“,2→ „Listening to X“,3→ „Watching X“,5→ „Competing in X“. Typ4(Custom) übernimmt deinstateunverändert ohne Präfix. Maximal 128 Zeichen inklusive Präfix. - Die Präfixe sind in jeder Sprache englisch, der Satz besteht zur Hälfte aus deinem eigenen Text, und nur unsere Hälfte zu übersetzen liest sich schlechter als eine einheitliche Zeile.
- Ein unbekannter
statuswird zuonline, nie ein Fehler, nie ein Verbindungsabbruch.sinceundafkwerden angenommen und ignoriert. - Es gibt keine Antwort. Erfolg ist still, genau wie bei Discord.
- Höchstens eine Änderung pro 5 Sekunden je Verbindung. Updates innerhalb des Fensters werden nicht abgelehnt, sondern überschreiben einander; angewendet wird der jeweils neueste Wert.
- Nur schreibend: dein Bot empfängt niemals
PRESENCE_UPDATE, unabhängig von den Intents.GUILD_PRESENCES(1 << 8) wird akzeptiert, liefert aber nichts, es existiert nur, damit Discord-kompatible Bibliotheken es anfordern können, ohne zu scheitern. - Dein Status wird automatisch alle 60 Sekunden erneuert und nach einer internen Neuverbindung wiederhergestellt, einmal gesetzt, bleibt er.
Verbindungsabbruch: RESUME (op 6)
Das READY enthält eine session_id und eine resume_gateway_url. Merke dir beides und zusätzlich die höchste empfangene Sequenznummer s aus den Dispatch-Frames. Reißt die Verbindung ab, öffnest du einen neuen WebSocket zur resume_gateway_url und schickst als erstes Frame statt eines IDENTIFY:
ws.send(JSON.stringify({ op: 6, d: { // RESUME
token: "ssbot_…",
session_id: "…aus dem READY…",
seq: lastSeq // höchstes empfangenes s
}}));
Gelingt der Resume, spielt das Gateway alle verpassten Events in Reihenfolge nach und schließt mit einem RESUMED-Dispatch ab. Ein zweites READY kommt nicht. Das Fenster dafür beträgt 90 Sekunden nach dem Abbruch, und der Puffer lebt im Speicher der Gateway-Instanz: Nach einem Neustart des Gateways (z. B. einem Deploy) ist jede Session weg.
Ist die Session nicht mehr fortsetzbar (Fenster abgelaufen, unbekannte session_id, zu alte seq, Gateway-Neustart, fehlerhaftes RESUME-Frame), antwortet das Gateway mit op 9 Invalid Session (d: false). Dann gilt: neue Verbindung, frisches IDENTIFY, weiter wie beim ersten Start. Discord-Bibliotheken machen genau das automatisch. Das Token wird bei jedem Resume vollständig neu geprüft, ein widerrufenes Token bekommt Close-Code 4004 und niemals ein Replay.
Close-Codes
| Code | Bedeutung | Was tun |
|---|---|---|
4004 | Authentifizierung fehlgeschlagen, Token ungültig oder widerrufen (auch mitten in der Session) | Nicht automatisch neu verbinden, erst das Token prüfen |
4008 | Rate-Limit: zu viele IDENTIFYs oder zu viele gleichzeitige Sessions | Warten, dann erneut verbinden |
4009 | Session-Timeout: Heartbeat blieb aus (getrennt bei 1,5× dem angesagten Intervall) | Neu verbinden, ein RESUME innerhalb des 90-Sekunden-Fensters funktioniert |
Alle anderen Abbrüche (Netz weg, Gateway-Shutdown) kommen ohne eigenen Code als gewöhnlicher Socket-Close, auch dann zuerst RESUME versuchen und bei Invalid Session neu identifizieren.
Rate-Limits
Limits sind pro Bot und pro Server gebündelt. Jede Antwort trägt die Zähler; ein 429 nennt die Wartezeit.
| Header | Bedeutung |
|---|---|
X-RateLimit-Limit | Anfragen pro Fenster |
X-RateLimit-Remaining | im aktuellen Fenster noch frei |
X-RateLimit-Reset-After | Sekunden bis zum Zurücksetzen |
Retry-After | bei 429: so lange warten |
Ein 429-Body ist Discord-förmig: { "message": "…", "retry_after": 7.457, "global": false }. Respektiere Retry-After, verbreitete Bibliotheken tun das automatisch.
Fehler
Fehler kommen als JSON mit sprechendem Code, kompatibel zu Discords { message, code }-Schema.
| HTTP | Bedeutung |
|---|---|
401 | Token fehlt, ungültig oder widerrufen |
403 | Bot fehlt die Berechtigung (Rolle/Rechte) |
404 | Ressource nicht gefunden, oder außerhalb der Reichweite des Bots (bewusst ununterscheidbar, damit keine fremden IDs erraten werden) |
400 | Eingabe ungültig (z. B. zu lang, Null-Byte, falsches Feld) |
429 | Rate-Limit, siehe Retry-After |
Bibliotheken portieren
Weil die API dem verbreiteten Bot-Standard folgt, laufen bestehende Bibliotheken oft mit drei Anpassungen:
- Basis-URL auf
https://api.speakspeak.net/api/v1und das Gateway aufwss://api.speakspeak.net/api/v1/gatewaysetzen. - ID-Format: SpeakSpeak nutzt UUIDs statt 64-Bit-Snowflakes, Code, der IDs als Zahl behandelt, muss sie als String führen.
- Berechtigungs-Bits gegen SpeakSpeaks Rechte-Modell prüfen.
Die vollständige API-Referenz – alle Endpunkte, Objekte, Fehlercodes, Rate-Limits und Gateway-Intents – findest du unter speakspeak.net/api. Porting-Fragen (etwa zu einzelnen Berechtigungs-Bits) beantworten wir direkt: contact@speakspeak.net.
Öffentliche Beta. Die Bot-API ist verfügbar und wird erweitert. Fragen, Feedback oder ein Bibliotheks-Wunsch? Schreib an contact@speakspeak.net.