← Zpět na blog
8. května 2026·4 min čtení·Tým TopSMS

Webhooky v SMS API: praktický průvodce

Jak fungují delivery webhooky, co posílají, jak je v aplikaci přijmout a zabezpečit. Příklady v PHP, Node.js a Pythonu + nejčastější integrace.

Webhook je HTTP POST z naší strany na vaši URL, kdykoliv se něco stane s SMS, kterou jste odeslali — typicky když operátor potvrdí doručení nebo chybu. Bez webhooku byste museli pollovat stav každé SMS (zbytečně), s webhookem se to dozvíte v reálném čase.

V tomto článku ukážu, jak webhook přijmout a zpracovat s příklady v 3 jazycích, a jak zajistit, že je bezpečný.

Co webhook obsahuje

Když pošlete SMS přes API, dostanete externalId. Když se SMS doručí (nebo selže), na vaši webhook URL přijde POST:

{
  "externalId": "msg-12345",
  "to": "+420608030884",
  "status": "delivered",
  "deliveredAt": "2026-05-08T14:23:45Z",
  "operatorCost": 0.028,
  "carrier": "230.2"
}

Možné status hodnoty:

StatusVýznam
sentPředáno operátorovi
deliveredDoručeno na telefon
failedSelhalo (špatné číslo, vypnuto, atd.)
expiredVypršelo (telefon byl nedostupný 24h+)
rejectedOdmítnuto operátorem

carrier je MCC.MNC code operátora (230.1 = T-Mobile CZ, 230.2 = O2 CZ, 230.3 = Vodafone CZ).

Jak nastavit webhook URL

V dashboardu API klíčů u svého API klíče vyplňte Webhook URL:

https://muj-eshop.cz/api/topsms-webhook

Pak vám každá delivery report přistane na tuto URL.

Přijem webhooku — PHP

<?php
// /api/topsms-webhook.php

$payload = file_get_contents('php://input');
$data = json_decode($payload, true);

if (!$data) {
    http_response_code(400);
    exit('Invalid JSON');
}

// Aktualizace v DB
$pdo = new PDO('mysql:host=localhost;dbname=eshop', 'user', 'pass');
$stmt = $pdo->prepare(
    'UPDATE messages SET status = ?, delivered_at = ?, operator_cost = ? WHERE external_id = ?'
);
$stmt->execute([
    $data['status'],
    $data['deliveredAt'] ?? null,
    $data['operatorCost'] ?? null,
    $data['externalId'],
]);

http_response_code(200);
echo 'OK';

Přijem webhooku — Node.js (Express)

// /api/topsms-webhook.js
const express = require('express')
const app = express()
app.use(express.json())

app.post('/api/topsms-webhook', async (req, res) => {
  const { externalId, status, deliveredAt, operatorCost } = req.body
  
  await db.message.update({
    where: { externalId },
    data: { status, deliveredAt, operatorCost }
  })
  
  res.status(200).send('OK')
})

app.listen(3000)

Přijem webhooku — Python (Flask)

from flask import Flask, request, jsonify
import sqlite3

app = Flask(__name__)

@app.route('/api/topsms-webhook', methods=['POST'])
def webhook():
    data = request.get_json()
    if not data:
        return 'Invalid JSON', 400
    
    conn = sqlite3.connect('eshop.db')
    conn.execute(
        'UPDATE messages SET status = ?, delivered_at = ?, operator_cost = ? WHERE external_id = ?',
        (data['status'], data.get('deliveredAt'), data.get('operatorCost'), data['externalId'])
    )
    conn.commit()
    conn.close()
    
    return 'OK', 200

if __name__ == '__main__':
    app.run(port=3000)

Bezpečnost — proč webhook URL musí být tajná

Pokud někdo zná vaši webhook URL, může na ni posílat falešné delivery reporty. Vy si pak myslíte, že SMS byla doručena, ale nebyla.

Tři vrstvy ochrany:

1. URL s tajemstvím

Místo /api/topsms-webhook použijte /api/topsms-webhook?secret=N0km7BGj5eWj8HLxyRBuxeiC4_CjYuim. V aplikaci ověřte secret:

if ($_GET['secret'] !== 'N0km7BGj5eWj8HLxyRBuxeiC4_CjYuim') {
    http_response_code(403);
    exit;
}

2. IP whitelist

Naše webhook služby posílají z konkrétních IP. V dashboardu najdete aktuální seznam. Ve své aplikaci povolte jen tyto IP:

$allowedIPs = ['142.93.207.11', '142.93.207.12'];
if (!in_array($_SERVER['REMOTE_ADDR'], $allowedIPs)) {
    http_response_code(403);
    exit;
}

3. HMAC signature (pokročilé)

Pokud potřebujete nejvyšší úroveň zabezpečení, vyžádejte si HMAC podpis. Každý webhook bude mít hlavičku X-TopSMS-Signature se SHA-256 podpisem payloadu. Vy si ji v aplikaci ověříte:

$body = file_get_contents('php://input');
$sig = $_SERVER['HTTP_X_TOPSMS_SIGNATURE'] ?? '';
$expected = hash_hmac('sha256', $body, 'your-shared-secret');

if (!hash_equals($expected, $sig)) {
    http_response_code(403);
    exit;
}

HMAC mode si můžete aktivovat per API klíč (kontaktujte podporu).

Retry logika — co dělat, když webhook selže

Pokud vaše endpoint vrátí HTTP 5xx, opakujeme webhook 3× s exponential backoff:

PokusLatence
1T+0
2T+30s
3T+5min
4T+30min

Po 4 neúspěšných pokusech webhook vyhodíme a delivery report najdete pouze přes GET /api/sms/status/.

Doporučení:

  • Vraťte HTTP 200 OK rychle — webhook handler nesmí trvat víc než 5 sekund.
  • Async zpracování — uložte payload do queue (Redis, RabbitMQ) a hned vraťte 200. Skutečné zpracování dělejte na pozadí.

Idempotence — jak nezpůsobit duplikáty

Můžeme webhook poslat (pokud první přišel, ale vy jste odpověděli pomalu a my retryovali). Vaše aplikace by měla:

# Check, jestli jsme webhook už zpracovali
existing = db.message.find_one({'externalId': data['externalId'], 'status': data['status']})
if existing:
    return 'OK', 200  # Idempotent: nic neudělat, jen vrátit 200

externalId + status kombinace je unikátní per událost.

Testování webhooku

V dev prostředí těžko zatestujete webhook na svém localhost. Použijte tunneling službu:

ngrok (free)

ngrok http 3000
# Vrátí: https://abc123.ngrok-free.app

Nastavte v dashboardu webhook URL na https://abc123.ngrok-free.app/api/topsms-webhook. Při testovacím SMS dostanete webhook na localhost.

localtunnel (alternativa)

npx localtunnel --port 3000

Webhook.site (testovací bin)

webhook.site — dostanete unikátní URL, kam můžeme posílat webhooky, a vidíte přijaté payloads ve webovém UI. Skvělé pro debug.

Časté chyby

1. Pomalá odpověd

Pokud webhook handler dělá pomalou operaci (volá další API, generuje PDF…), retry hází duplikáty. Vraťte 200 rychle, zpracujte později.

2. Bez ověření autenticity

Pokud webhook nemá ani secret, IP whitelist ani HMAC, kdokoliv může poslat falešný delivery report a zničit vaše statistiky.

3. Chybějící idempotence

Pokud na duplikátní webhook reagujete duplikátně (např. odešlete e-mail klientovi 2×), je to UX katastrofa. Vždy check existing record.

4. Webhook pro každou událost

Nepotřebujete webhook pro každý status (sent, delivered, failed, expired). Zpravidla stačí jen delivered a failed. Jinde nevíte, co dělat. V API klíčích si můžete v budoucnu vybrat, jaké stavy chcete.

Co dál

technicalapiwebhooky
Otestujte TopSMS zdarma

10 SMS na start, bez závazku, registrace minuta.

Registrovat zdarma →