telybot Sedikit tidak biasa.
Jauh lebih cerdas.
IndonesiaEnglish Masuk Hubungi kami Mulai gratis
Dokumentasi TelybotReferensi

Contoh server

Kode lengkap dan siap pakai untuk dua cara Telybot berkomunikasi dengan server Anda, dalam Node.js, Python, PHP, Go, Java, C#, dan Ruby: tindakan yang menjawab bot selama obrolan, dan webhook yang menerima kabar tentang apa yang terjadi di dalamnya.

Pilih bahasa Anda sekali saja: contoh di sini dan di panduan Tindakan akan mengikutinya.

Apa yang dilakukan contoh ini

  • POST /telybot/order-status menjawab tindakan. Bot mengirim nomor pesanan dan alamat email yang diketik pelanggan. Server menjawab 200 beserta status pesanan jika keduanya cocok, dan 404 jika tidak, sehingga bot meminta pelanggan memeriksa apa yang mereka ketik.
  • POST /telybot/events menerima webhook. Percobaan ulang membawa id yang sama, sehingga setiap event ditangani sekali berapa kali pun event itu tiba, dan jawabannya langsung 204.
  • Keduanya menolak, dengan 401, permintaan yang tanda tangannya tidak valid: yang tidak ditandatangani dengan rahasia Anda, diubah di tengah jalan, atau berumur lebih dari lima menit.

Sebelum Anda mulai

  1. Buka Bot Anda → Tindakan → Keamanan dan, di bawah Tanda tangan Telybot, tekan Tampilkan rahasia lalu salin. Rahasia itu diawali whsec_.
  2. Berikan ke server Anda sebagai pengaturan TELYBOT_SIGNING_SECRET, dari variabel lingkungan atau penyimpanan rahasia Anda. Jangan pernah menaruhnya di halaman web atau aplikasi.
  3. Di Tindakan → Tindakan baru, pilih POST dan alamat https://your-server.example/telybot/order-status, lalu minta pelanggan mengisi order_id (sebuah angka) dan email (alamat email).
  4. Di Tindakan → Webhook, tambahkan https://your-server.example/telybot/events dengan event yang Anda inginkan.

Kodenya

// 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

Pesanan dan ID event disimpan di memori agar contoh ini dapat berjalan sendiri. Simpan di database Anda: ID event yang tetap ada setelah restart tetap dapat mencegah duplikat.

Yang penting di kode Anda sendiri

  • Periksa body yang Anda terima, byte demi byte. Baca body mentah dan periksa tanda tangan sebelum Anda mem-parsing-nya. JSON yang sudah dibaca lalu ditulis ulang memiliki spasi yang berbeda, dan tidak lagi cocok. Di Express itu adalah express.raw(), di Flask request.get_data(), di PHP php://input, di Spring body byte[], di ASP.NET Core Request.Body, dan di Rails request.raw_post.
  • Selisih maksimal lima menit, maju atau mundur. Permintaan yang stempel waktunya lebih jauh dari jam server Anda akan ditolak, sehingga permintaan yang direkam tidak dapat dikirim ulang nanti. Biarkan jam server Anda diatur secara otomatis.
  • Bandingkan dalam waktu konstan. Setiap contoh membandingkan tanda tangan dengan fungsi waktu konstan dari bahasanya, sehingga lamanya pemeriksaan tidak mengungkapkan apa pun tentang seberapa dekat sebuah tebakan.
  • Salah satu tanda tangan. webhook-signature dapat berisi beberapa tanda tangan yang dipisahkan spasi. Permintaan itu asli jika salah satunya cocok.
  • Jawab dengan cepat. Bot menunggu 8 detik untuk tindakan, dan webhook menunggu 10 detik. Untuk webhook, jawab dulu lalu kerjakan pekerjaan yang lambat setelahnya, dari antrean.
  • Katakan “tidak ditemukan” dengan cara yang sama. Jawab 404 baik saat pesanan tidak ada maupun saat email tidak cocok, sehingga tidak ada yang bisa mengetahui nomor pesanan mana yang asli.

Pustaka

Tanda tangan mengikuti Standard Webhooks, yang pustakanya untuk bahasa-bahasa ini dan lainnya memeriksanya untuk Anda: berikan rahasia persis seperti yang ditampilkan, termasuk whsec_, dan body persis seperti saat diterima.

Pelanggan yang sudah masuk

Untuk memberi tahu bot siapa pelanggan yang sudah masuk, server Anda menandatangani token singkat untuk mereka dengan rahasia yang berbeda, yaitu rahasia identitas. Caranya, beserta kode dalam bahasa-bahasa yang sama, ada di Pengunjung terverifikasi.

Jika tidak berfungsi

  • Setiap permintaan mendapat 401. Pastikan server memiliki rahasia penandatanganan, yang diawali whsec_, bukan rahasia identitas, yang diawali tbid_. Buat yang baru di bawah Tanda tangan Telybot langsung menghentikan rahasia lama, jadi segera perbarui server Anda setelahnya. Dan periksa body sebelum mem-parsing-nya.
  • Sebagian permintaan mendapat 401. Jam server Anda meleset lebih dari lima menit.
  • Bot mengatakan tidak dapat mencari sesuatu. Tindakan → Semua tindakan menunjukkan mengapa panggilan terakhir gagal. Tekan Uji untuk mengirim panggilan sendiri dan melihat persis apa yang dikembalikan.
  • Webhook berhenti. Setelah lima puluh pengiriman gagal berturut-turut, webhook dijeda dan pemiliknya dikirimi email. Perbaiki penerimanya, tekan Kirim event uji coba, lalu Lanjutkan. Pengiriman menampilkan jawaban penerima Anda selama tiga puluh hari.
Berikutnya: Batasan dan keamanan →