Serverbeispiele
Vollständiger, funktionierender Code für die beiden Wege, auf denen Telybot mit Ihrem Server spricht, in Node.js, Python, PHP, Go, Java, C# und Ruby: eine Aktion, die dem Bot während eines Chats antwortet, und ein Webhook, der erfährt, was darin passiert ist.
Wählen Sie Ihre Sprache einmal: Die Beispiele hier und im Leitfaden zu Aktionen richten sich danach.
Was das Beispiel macht
POST /telybot/order-statusbeantwortet eine Aktion. Der Bot sendet die Bestellnummer und die E-Mail-Adresse, die der Kunde eingegeben hat. Der Server antwortet mit200und dem Status der Bestellung, wenn beides übereinstimmt, und mit404, wenn nicht, sodass der Bot den Kunden bittet, seine Eingabe zu prüfen.POST /telybot/eventsempfängt Webhooks. Ein erneuter Versuch enthält denselbenid-Wert, sodass jedes Ereignis nur einmal verarbeitet wird, egal wie oft es ankommt, und die Antwort ist sofort204.- Beide lehnen mit
401eine Anfrage ab, deren Signatur nicht stimmt: eine, die nicht mit Ihrem Secret signiert, unterwegs verändert wurde oder älter als fünf Minuten ist.
Bevor Sie beginnen
- Öffnen Sie Ihr Bot → Aktionen → Sicherheit, drücken Sie unter Signatur von Telybot auf Geheimnis anzeigen und kopieren Sie es. Es beginnt mit
whsec_. - Geben Sie es Ihrem Server als Einstellung
TELYBOT_SIGNING_SECRET, aus einer Umgebungsvariablen oder Ihrem Secret-Speicher. Fügen Sie es nie in eine Webseite oder eine App ein. - Wählen Sie unter Aktionen → Neue Aktion
POSTund die Adressehttps://your-server.example/telybot/order-status, und fragen Sie den Kunden nachorder_id(eine Zahl) undemail(eine E-Mail-Adresse). - Fügen Sie unter Aktionen → Webhooks
https://your-server.example/telybot/eventsmit den gewünschten Ereignissen hinzu.
Der Code
// npm install express
const crypto = require('crypto');
const express = require('express');
const SECRET = process.env.TELYBOT_SIGNING_SECRET; // whsec_…, from Actions → Security
// True when your bot signed this request in the last five minutes.
function isFromTelyBot(headers, rawBody, secret) {
const id = headers['webhook-id'];
const timestamp = Number(headers['webhook-timestamp']);
const now = Math.floor(Date.now() / 1000);
if (!id || !(Math.abs(now - timestamp) <= 300)) return false;
const key = Buffer.from(secret.replace(/^whsec_/, ''), 'base64');
const expected = crypto.createHmac('sha256', key)
.update(`${id}.${headers['webhook-timestamp']}.`).update(rawBody).digest();
return String(headers['webhook-signature'] || '').split(' ').some((part) => {
const given = Buffer.from(part.replace(/^v1,/, ''), 'base64');
return part.startsWith('v1,') && given.length === expected.length
&& crypto.timingSafeEqual(given, expected);
});
}
const app = express();
// The exact bytes Telybot signed: never parse and re-serialise before checking.
app.use('/telybot', express.raw({ type: () => true }));
const bodyOf = (req) => (Buffer.isBuffer(req.body) ? req.body : Buffer.alloc(0));
const orders = new Map([['48213', { email: 'asha@example.com', status: 'shipped', eta: '2 October' }]]);
const seen = new Set(); // ids of events already handled: keep these in your database
// An action: during a chat, the bot asks where an order is.
app.post('/telybot/order-status', (req, res) => {
if (!isFromTelyBot(req.headers, bodyOf(req), SECRET)) return res.sendStatus(401);
const asked = JSON.parse(bodyOf(req).toString('utf8'));
const order = orders.get(asked.order_id);
if (!order || order.email !== String(asked.email || '').trim().toLowerCase()) {
return res.status(404).json({ error: 'not_found' }); // the bot asks the customer to check
}
res.json({ status: order.status, eta: order.eta });
});
// A webhook: something happened in a chat.
app.post('/telybot/events', (req, res) => {
if (!isFromTelyBot(req.headers, bodyOf(req), SECRET)) return res.sendStatus(401);
const event = JSON.parse(bodyOf(req).toString('utf8'));
if (!seen.has(event.id)) { // a retry carries the same id
seen.add(event.id);
console.log(event.type, event.data); // hand it to your CRM or a queue
}
res.sendStatus(204);
});
app.listen(3000);# pip install flask
import base64
import hashlib
import hmac
import json
import os
import time
from flask import Flask, request
SECRET = os.environ["TELYBOT_SIGNING_SECRET"] # whsec_…, from Actions → Security
def is_from_telybot(headers, raw_body: bytes, secret: str) -> bool:
"""True when your bot signed this request in the last five minutes."""
msg_id = headers.get("webhook-id", "")
timestamp = headers.get("webhook-timestamp", "")
try:
if not msg_id or abs(time.time() - int(timestamp)) > 300:
return False
except ValueError:
return False
key = base64.b64decode(secret.removeprefix("whsec_"))
signed = f"{msg_id}.{timestamp}.".encode() + raw_body
expected = base64.b64encode(hmac.new(key, signed, hashlib.sha256).digest())
return any(
part.startswith("v1,") and hmac.compare_digest(part[3:].encode(), expected)
for part in headers.get("webhook-signature", "").split()
)
app = Flask(__name__)
ORDERS = {"48213": {"email": "asha@example.com", "status": "shipped", "eta": "2 October"}}
seen = set() # ids of events already handled: keep these in your database
# An action: during a chat, the bot asks where an order is.
@app.post("/telybot/order-status")
def order_status():
raw = request.get_data() # the exact bytes Telybot signed
if not is_from_telybot(request.headers, raw, SECRET):
return "", 401
asked = json.loads(raw)
order = ORDERS.get(asked.get("order_id", ""))
if not order or order["email"] != asked.get("email", "").strip().lower():
return {"error": "not_found"}, 404 # the bot asks the customer to check
return {"status": order["status"], "eta": order["eta"]}
# A webhook: something happened in a chat.
@app.post("/telybot/events")
def events():
raw = request.get_data()
if not is_from_telybot(request.headers, raw, SECRET):
return "", 401
event = json.loads(raw)
if event["id"] not in seen: # a retry carries the same id
seen.add(event["id"])
print(event["type"], event["data"]) # hand it to your CRM or a queue
return "", 204
if __name__ == "__main__":
app.run(port=3000)<?php
// Plain PHP 8. In Laravel, $request->getContent() is the raw body and
// $request->header('webhook-id') a header.
const ORDERS = ['48213' => ['email' => 'asha@example.com', 'status' => 'shipped', 'eta' => '2 October']];
/** True when your bot signed this request in the last five minutes. */
function is_from_telybot(string $id, string $timestamp, string $signatures, string $rawBody, string $secret): bool
{
if ($id === '' || !preg_match('/^\d{1,12}$/', $timestamp) || abs(time() - (int) $timestamp) > 300) {
return false;
}
$key = base64_decode(preg_replace('/^whsec_/', '', $secret));
$expected = base64_encode(hash_hmac('sha256', "$id.$timestamp.$rawBody", $key, true));
foreach (explode(' ', $signatures) as $part) {
if (str_starts_with($part, 'v1,') && hash_equals($expected, substr($part, 3))) {
return true;
}
}
return false;
}
/** False when this event id was handled before: keep these in your database. */
function first_time(string $id): bool
{
$marker = sys_get_temp_dir() . '/telybot-event-' . hash('sha256', $id);
return ($handle = @fopen($marker, 'x')) !== false && fclose($handle);
}
$raw = file_get_contents('php://input'); // the exact bytes Telybot signed
$signed = is_from_telybot(
$_SERVER['HTTP_WEBHOOK_ID'] ?? '',
$_SERVER['HTTP_WEBHOOK_TIMESTAMP'] ?? '',
$_SERVER['HTTP_WEBHOOK_SIGNATURE'] ?? '',
$raw,
getenv('TELYBOT_SIGNING_SECRET'), // whsec_…, from Actions → Security
);
if (!$signed) {
http_response_code(401);
exit;
}
switch (parse_url($_SERVER['REQUEST_URI'], PHP_URL_PATH)) {
// An action: during a chat, the bot asks where an order is.
case '/telybot/order-status':
$asked = json_decode($raw, true);
$order = ORDERS[$asked['order_id'] ?? ''] ?? null;
header('Content-Type: application/json');
if (!$order || $order['email'] !== strtolower(trim($asked['email'] ?? ''))) {
http_response_code(404); // the bot asks the customer to check
echo json_encode(['error' => 'not_found']);
break;
}
echo json_encode(['status' => $order['status'], 'eta' => $order['eta']]);
break;
// A webhook: something happened in a chat.
case '/telybot/events':
$event = json_decode($raw, true);
if (first_time($event['id'])) { // a retry carries the same id
error_log($event['type'] . ' ' . json_encode($event['data'])); // hand it to your CRM or a queue
}
http_response_code(204);
break;
default:
http_response_code(404);
}package main
import (
"crypto/hmac"
"crypto/sha256"
"encoding/base64"
"encoding/json"
"io"
"log"
"net/http"
"os"
"strconv"
"strings"
"sync"
"time"
)
// isFromTelyBot reports whether your bot signed this request in the last five minutes.
func isFromTelyBot(h http.Header, body []byte, secret string) bool {
id, timestamp := h.Get("webhook-id"), h.Get("webhook-timestamp")
sent, err := strconv.ParseInt(timestamp, 10, 64)
now := time.Now().Unix()
if id == "" || err != nil || sent < now-300 || sent > now+300 {
return false
}
key, err := base64.StdEncoding.DecodeString(strings.TrimPrefix(secret, "whsec_"))
if err != nil {
return false
}
mac := hmac.New(sha256.New, key)
mac.Write([]byte(id + "." + timestamp + "."))
mac.Write(body)
expected := mac.Sum(nil)
for _, part := range strings.Fields(h.Get("webhook-signature")) {
given, err := base64.StdEncoding.DecodeString(strings.TrimPrefix(part, "v1,"))
if strings.HasPrefix(part, "v1,") && err == nil && hmac.Equal(given, expected) {
return true
}
}
return false
}
type order struct{ Email, Status, ETA string }
var orders = map[string]order{"48213": {"asha@example.com", "shipped", "2 October"}}
var (
mu sync.Mutex
seen = map[string]bool{} // ids of events already handled: keep these in your database
)
func main() {
secret := os.Getenv("TELYBOT_SIGNING_SECRET") // whsec_…, from Actions → Security
// signed reads the exact bytes Telybot signed and refuses anything else.
signed := func(next func(http.ResponseWriter, []byte)) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
body, err := io.ReadAll(http.MaxBytesReader(w, r.Body, 1<<20))
if err != nil || !isFromTelyBot(r.Header, body, secret) {
w.WriteHeader(http.StatusUnauthorized)
return
}
next(w, body)
}
}
// An action: during a chat, the bot asks where an order is.
http.HandleFunc("POST /telybot/order-status", signed(func(w http.ResponseWriter, body []byte) {
var asked struct {
OrderID string `json:"order_id"`
Email string `json:"email"`
}
json.Unmarshal(body, &asked)
w.Header().Set("Content-Type", "application/json")
o, ok := orders[asked.OrderID]
if !ok || o.Email != strings.ToLower(strings.TrimSpace(asked.Email)) {
w.WriteHeader(http.StatusNotFound) // the bot asks the customer to check
json.NewEncoder(w).Encode(map[string]string{"error": "not_found"})
return
}
json.NewEncoder(w).Encode(map[string]string{"status": o.Status, "eta": o.ETA})
}))
// A webhook: something happened in a chat.
http.HandleFunc("POST /telybot/events", signed(func(w http.ResponseWriter, body []byte) {
var event struct {
ID string `json:"id"`
Type string `json:"type"`
Data json.RawMessage `json:"data"`
}
json.Unmarshal(body, &event)
mu.Lock()
first := !seen[event.ID] // a retry carries the same id
seen[event.ID] = true
mu.Unlock()
if first {
log.Printf("%s %s", event.Type, event.Data) // hand it to your CRM or a queue
}
w.WriteHeader(http.StatusNoContent)
}))
log.Fatal(http.ListenAndServe(":3000", nil))
}// Spring Boot 4 (spring-boot-starter-webmvc)
package com.example.telybot;
import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
import java.time.Instant;
import java.util.Base64;
import java.util.Locale;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestHeader;
import org.springframework.web.bind.annotation.RestController;
import tools.jackson.databind.JsonNode;
import tools.jackson.databind.json.JsonMapper;
@SpringBootApplication
@RestController
public class TelyBotReceiver {
static final String SECRET = System.getenv("TELYBOT_SIGNING_SECRET"); // whsec_…, from Actions → Security
static final Map<String, Map<String, String>> ORDERS =
Map.of("48213", Map.of("email", "asha@example.com", "status", "shipped", "eta", "2 October"));
final Set<String> seen = ConcurrentHashMap.newKeySet(); // ids of events already handled: keep these in your database
final JsonMapper json = JsonMapper.builder().build();
public static void main(String[] args) {
SpringApplication.run(TelyBotReceiver.class, args);
}
/** True when your bot signed this request in the last five minutes. */
static boolean isFromTelyBot(String id, String timestamp, String signatures, byte[] body, String secret)
throws Exception {
long now = Instant.now().getEpochSecond();
long sent;
try {
sent = Long.parseLong(timestamp);
} catch (NumberFormatException notANumber) {
return false;
}
if (id == null || signatures == null || sent < now - 300 || sent > now + 300) return false;
Mac mac = Mac.getInstance("HmacSHA256");
mac.init(new SecretKeySpec(Base64.getDecoder().decode(secret.replaceFirst("^whsec_", "")), "HmacSHA256"));
mac.update((id + "." + timestamp + ".").getBytes(StandardCharsets.UTF_8));
byte[] expected = mac.doFinal(body);
for (String part : signatures.split(" ")) {
try {
if (part.startsWith("v1,") && MessageDigest.isEqual(Base64.getDecoder().decode(part.substring(3)), expected)) {
return true;
}
} catch (IllegalArgumentException notBase64) {
// not a signature of ours
}
}
return false;
}
static boolean signed(HttpHeaders headers, byte[] body) throws Exception {
return isFromTelyBot(headers.getFirst("webhook-id"), headers.getFirst("webhook-timestamp"),
headers.getFirst("webhook-signature"), body, SECRET);
}
// An action: during a chat, the bot asks where an order is.
@PostMapping("/telybot/order-status")
ResponseEntity<Map<String, String>> orderStatus(@RequestHeader HttpHeaders headers, @RequestBody byte[] body)
throws Exception { // the exact bytes Telybot signed
if (!signed(headers, body)) return ResponseEntity.status(HttpStatus.UNAUTHORIZED).build();
JsonNode asked = json.readTree(body);
Map<String, String> order = ORDERS.get(asked.path("order_id").asString());
if (order == null || !order.get("email").equals(asked.path("email").asString().trim().toLowerCase(Locale.ROOT))) {
// the bot asks the customer to check
return ResponseEntity.status(HttpStatus.NOT_FOUND).body(Map.of("error", "not_found"));
}
return ResponseEntity.ok(Map.of("status", order.get("status"), "eta", order.get("eta")));
}
// A webhook: something happened in a chat.
@PostMapping("/telybot/events")
ResponseEntity<Void> events(@RequestHeader HttpHeaders headers, @RequestBody byte[] body) throws Exception {
if (!signed(headers, body)) return ResponseEntity.status(HttpStatus.UNAUTHORIZED).build();
JsonNode event = json.readTree(body);
if (seen.add(event.path("id").asString())) { // a retry carries the same id
System.out.println(event.path("type").asString() + " " + event.path("data")); // hand it to your CRM or a queue
}
return ResponseEntity.noContent().build();
}
}// ASP.NET Core minimal API (.NET 8 or later)
using System.Collections.Concurrent;
using System.Security.Cryptography;
using System.Text;
using System.Text.Json;
var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();
var secret = builder.Configuration["TELYBOT_SIGNING_SECRET"]!; // whsec_…, from Actions → Security
var orders = new Dictionary<string, Order> { ["48213"] = new("asha@example.com", "shipped", "2 October") };
var seen = new ConcurrentDictionary<string, bool>(); // ids of events already handled: keep these in your database
// An action: during a chat, the bot asks where an order is.
app.MapPost("/telybot/order-status", async (HttpRequest request) =>
{
var body = await ReadBody(request); // the exact bytes Telybot signed
if (!IsFromTelyBot(request.Headers, body, secret)) return Results.Unauthorized();
var asked = JsonSerializer.Deserialize<Dictionary<string, string>>(body) ?? new();
if (!orders.TryGetValue(asked.GetValueOrDefault("order_id", ""), out var order)
|| order.Email != asked.GetValueOrDefault("email", "").Trim().ToLowerInvariant())
{
return Results.NotFound(new { error = "not_found" }); // the bot asks the customer to check
}
return Results.Ok(new { status = order.Status, eta = order.Eta });
});
// A webhook: something happened in a chat.
app.MapPost("/telybot/events", async (HttpRequest request) =>
{
var body = await ReadBody(request);
if (!IsFromTelyBot(request.Headers, body, secret)) return Results.Unauthorized();
using var json = JsonDocument.Parse(body);
var e = json.RootElement;
if (seen.TryAdd(e.GetProperty("id").GetString()!, true)) // a retry carries the same id
{
Console.WriteLine($"{e.GetProperty("type").GetString()} {e.GetProperty("data")}"); // hand it to your CRM or a queue
}
return Results.NoContent();
});
app.Run("http://localhost:3000");
static async Task<byte[]> ReadBody(HttpRequest request)
{
using var buffer = new MemoryStream();
await request.Body.CopyToAsync(buffer);
return buffer.ToArray();
}
// True when your bot signed this request in the last five minutes.
static bool IsFromTelyBot(IHeaderDictionary headers, byte[] body, string secret)
{
string id = headers["webhook-id"].ToString(), timestamp = headers["webhook-timestamp"].ToString();
long now = DateTimeOffset.UtcNow.ToUnixTimeSeconds();
if (id.Length == 0 || !long.TryParse(timestamp, out var sent) || sent < now - 300 || sent > now + 300) return false;
var key = Convert.FromBase64String(secret.StartsWith("whsec_") ? secret[6..] : secret);
var signed = Encoding.UTF8.GetBytes($"{id}.{timestamp}.").Concat(body).ToArray();
var expected = HMACSHA256.HashData(key, signed);
foreach (var part in headers["webhook-signature"].ToString().Split(' '))
{
var given = new byte[32];
if (part.StartsWith("v1,") && Convert.TryFromBase64String(part[3..], given, out var length)
&& length == given.Length && CryptographicOperations.FixedTimeEquals(given, expected))
{
return true;
}
}
return false;
}
record Order(string Email, string Status, string Eta);# gem install sinatra rackup puma
require 'json'
require 'openssl'
require 'sinatra'
set :port, 3000
SECRET = ENV.fetch('TELYBOT_SIGNING_SECRET') # whsec_…, from Actions → Security
ORDERS = { '48213' => { email: 'asha@example.com', status: 'shipped', eta: '2 October' } }.freeze
SEEN = Set.new # ids of events already handled: keep these in your database
# True when your bot signed this request in the last five minutes.
def from_telybot?(id, timestamp, signatures, raw_body, secret)
return false if id.to_s.empty? || !timestamp.to_s.match?(/\A\d+\z/) || (Time.now.to_i - timestamp.to_i).abs > 300
key = secret.delete_prefix('whsec_').unpack1('m')
expected = [OpenSSL::HMAC.digest('SHA256', key, "#{id}.#{timestamp}.".b + raw_body.b)].pack('m0')
signatures.to_s.split(' ').any? { |part| part.start_with?('v1,') && OpenSSL.secure_compare(part[3..], expected) }
end
before '/telybot/*' do
@raw = request.body.read # the exact bytes Telybot signed
halt 401 unless from_telybot?(request.env['HTTP_WEBHOOK_ID'], request.env['HTTP_WEBHOOK_TIMESTAMP'],
request.env['HTTP_WEBHOOK_SIGNATURE'], @raw, SECRET)
end
# An action: during a chat, the bot asks where an order is.
post '/telybot/order-status' do
asked = JSON.parse(@raw)
order = ORDERS[asked['order_id']]
content_type :json
unless order && order[:email] == asked['email'].to_s.strip.downcase
halt 404, { error: 'not_found' }.to_json # the bot asks the customer to check
end
{ status: order[:status], eta: order[:eta] }.to_json
end
# A webhook: something happened in a chat.
post '/telybot/events' do
event = JSON.parse(@raw)
if SEEN.add?(event['id']) # a retry carries the same id
puts "#{event['type']} #{event['data']}" # hand it to your CRM or a queue
end
204
endDie Bestellungen und die Ereignis-IDs werden im Arbeitsspeicher gehalten, damit das Beispiel eigenständig läuft. Speichern Sie sie in Ihrer Datenbank: Eine Ereignis-ID, die einen Neustart übersteht, verhindert auch danach noch ein Duplikat.
Worauf es in Ihrem eigenen Code ankommt
- Prüfen Sie den empfangenen Body, Byte für Byte. Lesen Sie den Roh-Body und prüfen Sie die Signatur, bevor Sie ihn parsen. JSON, das eingelesen und wieder ausgegeben wurde, hat andere Leerzeichen und stimmt nicht mehr überein. In Express ist das
express.raw(), in Flaskrequest.get_data(), in PHPphp://input, in Spring einbyte[]-Body, in ASP.NET CoreRequest.Bodyund in Railsrequest.raw_post. - Fünf Minuten in beide Richtungen. Eine Anfrage, deren Zeitstempel weiter von der Uhr Ihres Servers abweicht, wird abgelehnt, sodass eine aufgezeichnete Anfrage später nicht erneut gesendet werden kann. Lassen Sie die Uhr Ihres Servers automatisch stellen.
- Vergleichen Sie in konstanter Zeit. Jedes Beispiel vergleicht Signaturen mit der Constant-Time-Funktion seiner Sprache, sodass die Dauer einer Prüfung nichts darüber verrät, wie nah ein Rateversuch war.
- Eine beliebige der Signaturen.
webhook-signaturekann mehrere, durch Leerzeichen getrennte Signaturen enthalten. Die Anfrage ist echt, wenn eine davon übereinstimmt. - Antworten Sie schnell. Der Bot wartet bei einer Aktion 8 Sekunden, ein Webhook wartet 10. Antworten Sie bei einem Webhook zuerst und erledigen Sie die langsame Arbeit danach, aus einer Warteschlange.
- Sagen Sie „nicht gefunden“ immer gleich. Antworten Sie mit
404, sowohl wenn die Bestellung nicht existiert als auch wenn die E-Mail-Adresse nicht übereinstimmt, damit niemand herausfinden kann, welche Bestellnummern echt sind.
Bibliotheken
Die Signatur folgt Standard Webhooks, dessen Bibliotheken für diese und weitere Sprachen sie für Sie prüfen: Übergeben Sie ihnen das Secret wie angezeigt, einschließlich whsec_, und den Body genau so, wie er angekommen ist.
Angemeldete Kunden
Um dem Bot mitzuteilen, wer ein angemeldeter Kunde ist, signiert Ihr Server mit einem anderen Secret, dem Identitäts-Secret, ein kurzes Token für ihn. Wie das geht, mit Code in denselben Sprachen, steht unter Verifizierte Besucher.
Wenn es nicht funktioniert
- Jede Anfrage erhält
401. Prüfen Sie, ob der Server das Signatur-Secret hat, das mitwhsec_beginnt, und nicht das Identitäts-Secret, das mittbid_beginnt. Neues erstellen unter Signatur von Telybot deaktiviert das alte Secret sofort, aktualisieren Sie Ihren Server also direkt danach. Und prüfen Sie den Body, bevor Sie ihn parsen. - Manche Anfragen erhalten
401. Die Uhr Ihres Servers geht um mehr als fünf Minuten falsch. - Der Bot sagt, dass er etwas nicht nachschlagen konnte. Aktionen → Alle Aktionen zeigt, warum der letzte Aufruf fehlgeschlagen ist. Drücken Sie Testen, um selbst einen zu senden und genau zu sehen, was zurückkam.
- Ein Webhook wurde gestoppt. Nach fünfzig fehlgeschlagenen Zustellungen hintereinander wird er pausiert, und die Inhaber erhalten eine E-Mail. Beheben Sie das Problem beim Empfänger, drücken Sie Testereignis senden und dann Fortsetzen. Zustellungen listet dreißig Tage lang auf, was Ihr Empfänger geantwortet hat.