telybot Un po’ meno ordinario.
Molto più intelligente.
ItalyEnglish Accedi Parliamone Inizia gratis
Documentazione di TelybotRiferimento

Esempi di server

Codice completo e funzionante per i due modi in cui Telybot comunica con il tuo server, in Node.js, Python, PHP, Go, Java, C# e Ruby: un'azione che risponde al bot durante una chat e un webhook che viene informato di ciò che vi è accaduto.

Scegli il tuo linguaggio una volta: gli esempi qui e nella guida Azioni lo seguono.

Cosa fa l'esempio

  • POST /telybot/order-status risponde a un'azione. Il bot invia il numero dell'ordine e l'indirizzo email digitati dal cliente. Il server risponde 200 con lo stato dell'ordine quando entrambi corrispondono, e 404 quando non corrispondono, così il bot chiede al cliente di controllare ciò che ha digitato.
  • POST /telybot/events riceve i webhook. Un nuovo tentativo porta lo stesso id, così ogni evento viene gestito una sola volta, per quante volte arrivi, e la risposta è subito 204.
  • Entrambi rifiutano, con 401, una richiesta la cui firma non è valida: non firmata con il tuo segreto, modificata lungo il percorso o più vecchia di cinque minuti.

Prima di iniziare

  1. Apri Il tuo bot → Azioni → Sicurezza e, sotto Firma di Telybot, premi Mostra il segreto e copialo. Inizia con whsec_.
  2. Passalo al tuo server come impostazione TELYBOT_SIGNING_SECRET, da una variabile d'ambiente o dal tuo archivio di segreti. Non inserirlo mai in una pagina web o in un'app.
  3. In Azioni → Nuova azione, scegli POST e l'indirizzo https://your-server.example/telybot/order-status, e chiedi al cliente order_id (un numero) e email (un indirizzo email).
  4. In Azioni → Webhook, aggiungi https://your-server.example/telybot/events con gli eventi che desideri.

Il codice

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

Gli ordini e gli ID degli eventi sono tenuti in memoria perché l'esempio funzioni da solo. Conservali nel tuo database: un ID evento che sopravvive a un riavvio continua a bloccare un duplicato.

Cosa conta nel tuo codice

  • Verifica il corpo ricevuto, byte per byte. Leggi il corpo grezzo e verifica la firma prima di analizzarlo. Un JSON che è stato letto e riscritto ha spaziature diverse e non corrisponde più. In Express è express.raw(), in Flask request.get_data(), in PHP php://input, in Spring un corpo byte[], in ASP.NET Core Request.Body e in Rails request.raw_post.
  • Cinque minuti in un senso o nell'altro. Una richiesta il cui timestamp si discosta di più dall'orologio del tuo server viene rifiutata, così una richiesta registrata non può essere inviata di nuovo più tardi. Mantieni l'orologio del tuo server regolato automaticamente.
  • Confronta in tempo costante. Ogni esempio confronta le firme con la funzione a tempo costante del suo linguaggio, così il tempo impiegato da un controllo non rivela nulla su quanto un tentativo fosse vicino.
  • Una qualsiasi delle firme. webhook-signature può contenere più firme separate da spazi. La richiesta è autentica se una di esse corrisponde.
  • Rispondi rapidamente. Il bot attende un'azione per 8 secondi, e un webhook attende 10 secondi. Per un webhook, rispondi prima e fai il lavoro lento dopo, da una coda.
  • Di' «non trovato» sempre allo stesso modo. Rispondi 404 sia quando l'ordine non esiste sia quando l'email non corrisponde, così nessuno può scoprire quali numeri d'ordine sono reali.

Librerie

La firma segue Standard Webhooks, le cui librerie per questi e altri linguaggi la verificano per te: passa loro il segreto così come viene mostrato, whsec_ incluso, e il corpo esattamente come è arrivato.

Clienti che hanno effettuato l'accesso

Per dire al bot chi è un cliente che ha effettuato l'accesso, il tuo server firma per lui un breve token con un segreto diverso, il segreto di identità. Come farlo, con il codice negli stessi linguaggi, è spiegato in Visitatori verificati.

Quando non funziona

  • Ogni richiesta riceve 401. Verifica che il server abbia il segreto di firma, che inizia con whsec_, e non il segreto di identità, che inizia con tbid_. Creane uno nuovo in Firma di Telybot disattiva subito il vecchio segreto, quindi aggiorna il tuo server subito dopo. E verifica il corpo prima di analizzarlo.
  • Alcune richieste ricevono 401. L'orologio del tuo server è sfasato di più di cinque minuti.
  • Il bot dice di non essere riuscito a cercare qualcosa. Azioni → Tutte le azioni mostra perché l'ultima chiamata non è riuscita. Premi Test per inviarne una tu stesso e vedere esattamente cosa è tornato indietro.
  • Un webhook si è fermato. Dopo cinquanta consegne fallite consecutive viene messo in pausa e i proprietari ricevono un'email. Correggi il destinatario, premi Invia un evento di test, poi Riprendi. Consegne elenca cosa ha risposto il tuo destinatario, per trenta giorni.
Avanti: Limiti e sicurezza →