telybot Ein bisschen weniger gewöhnlich.
Ein ganzes Stück intelligenter.
AustriaEnglish Anmelden Mit uns sprechen Kostenlos starten
Telybot DokumentationReferenz

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-status beantwortet eine Aktion. Der Bot sendet die Bestellnummer und die E-Mail-Adresse, die der Kunde eingegeben hat. Der Server antwortet mit 200 und dem Status der Bestellung, wenn beides übereinstimmt, und mit 404, wenn nicht, sodass der Bot den Kunden bittet, seine Eingabe zu prüfen.
  • POST /telybot/events empfängt Webhooks. Ein erneuter Versuch enthält denselben id-Wert, sodass jedes Ereignis nur einmal verarbeitet wird, egal wie oft es ankommt, und die Antwort ist sofort 204.
  • Beide lehnen mit 401 eine 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

  1. Öffnen Sie Ihr Bot → Aktionen → Sicherheit, drücken Sie unter Signatur von Telybot auf Geheimnis anzeigen und kopieren Sie es. Es beginnt mit whsec_.
  2. 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.
  3. Wählen Sie unter Aktionen → Neue Aktion POST und die Adresse https://your-server.example/telybot/order-status, und fragen Sie den Kunden nach order_id (eine Zahl) und email (eine E-Mail-Adresse).
  4. Fügen Sie unter Aktionen → Webhooks https://your-server.example/telybot/events mit 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
end

Die 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 Flask request.get_data(), in PHP php://input, in Spring ein byte[]-Body, in ASP.NET Core Request.Body und in Rails request.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-signature kann 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 mit whsec_ beginnt, und nicht das Identitäts-Secret, das mit tbid_ 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.
Weiter: Limits und Sicherheit →