NotifyHub
Developer docs

API Reference

Twilio-style REST API. Una sola chiamata per inviare notifiche su WhatsApp, Telegram, Email, Push, Discord e Slack.

Quick start

  1. Crea il workspace su notify.trovido.com/register
  2. Genera una API key da /sources
  3. Fai la prima chiamata
  4. Verifica il delivery in /deliveries

Piano Free: 500 msg/mese gratis, 3 canali, nessuna carta richiesta.

curl -X POST https://notify.trovido.com/api/notifyhub/v1/send \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "recipient": {
      "name": "Mario Rossi",
      "email": "[email protected]"
    },
    "template": "hello_world",
    "channel": "email",
    "vars": { "name": "Mario" }
  }'
$response = Http::withToken('YOUR_API_KEY')
    ->post('https://notify.trovido.com/api/notifyhub/v1/send', [
        'recipient' => ['name' => 'Mario', 'email' => '[email protected]'],
        'template'  => 'hello_world',
        'channel'   => 'email',
        'vars'      => ['name' => 'Mario'],
    ]);
const res = await fetch('https://notify.trovido.com/api/notifyhub/v1/send', {
  method: 'POST',
  headers: {
    'Authorization': 'Bearer YOUR_API_KEY',
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
    recipient: { name: 'Mario', email: '[email protected]' },
    template: 'hello_world',
    channel: 'email',
    vars: { name: 'Mario' },
  }),
});
import requests

r = requests.post('https://notify.trovido.com/api/notifyhub/v1/send',
    headers={'Authorization': 'Bearer YOUR_API_KEY'},
    json={
        'recipient': {'name': 'Mario', 'email': '[email protected]'},
        'template': 'hello_world',
        'channel': 'email',
        'vars': {'name': 'Mario'},
    })
Risposta — 202 Accepted
{
  "status": "queued",
  "delivery_id": 12345,
  "channel": "email",
  "recipient": { "id": 1, "name": "Mario Rossi" },
  "template": "hello_world"
}
POST /api/notifyhub/v1/send

Invia una notifica su qualsiasi canale. Il destinatario viene creato automaticamente se non esiste.

Headers

AuthorizationrequiredBearer YOUR_API_KEY
Content-Typerequiredapplication/json

Body

recipientobject req — per ID (recipient.id) oppure per contatti (name + email/phone)
templatestring req — slug del template
channelstring — email | whatsapp | telegram (default: auto)
varsobject — sostituisce {{ "{{key}}" }} nel template
prioritystring — low | normal | high
scheduled_forISO 8601 — schedula invio futuro
idempotency_keystring (max 128) — stessa chiave entro 1h ritorna stesso delivery
actions[]array — bottoni interattivi (max 5) con slug, label, callback_url
mediaobject — file multimediale (vedi Media & allegati)
attachments[]array (max 5) — allegati email (vedi Media & allegati)
Email transazionale
{
  "recipient": { "name": "Mario", "email": "[email protected]" },
  "template": "order_confirmed",
  "channel": "email",
  "vars": { "order_id": "ORD-2024-1234" }
}
WhatsApp con bottoni interattivi
{
  "recipient": { "whatsapp_phone": "+393331234567" },
  "template": "review_request",
  "channel": "whatsapp",
  "actions": [
    { "slug": "approve", "label": "Pubblica",
      "callback_url": "https://sito.it/api/review/456" },
    { "slug": "reject", "label": "Rifiuta",
      "callback_url": "https://sito.it/api/review/456" }
  ]
}
Telegram con priorita alta
{
  "recipient": { "telegram_chat_id": "624419126" },
  "template": "alert_critical",
  "channel": "telegram",
  "priority": "high",
  "vars": { "service": "Mailer", "error": "SMTP timeout" }
}
POST /api/notifyhub/v1/send/batch

Invia fino a 100 messaggi in una sola chiamata. Ogni messaggio usa lo stesso formato di /send.

Body

messages[]array (1-100) req — ogni elemento ha recipient, template, vars, channel, ecc.

Risposta

Ritorna il conteggio totale + risultato per ogni messaggio. I messaggi vengono processati indipendentemente: un fallimento non blocca gli altri.

Request — 3 messaggi
{
  "messages": [
    { "recipient": { "id": 1 }, "template": "weekly_digest", "channel": "email" },
    { "recipient": { "id": 2 }, "template": "weekly_digest", "channel": "telegram" },
    { "recipient": { "id": 3 }, "template": "weekly_digest", "channel": "whatsapp" }
  ]
}
Response — 200 OK
{
  "total": 3,
  "queued": 3,
  "failed": 0,
  "results": [
    { "index": 0, "status": "queued", "delivery_id": 101 },
    { "index": 1, "status": "queued", "delivery_id": 102 },
    { "index": 2, "status": "queued", "delivery_id": 103 }
  ]
}

Media & allegati

Invia immagini, video, documenti e allegati insieme alle notifiche.

media (object, opzionale)

media.urlstring req — URL pubblica del file
media.typestring — image | video | document (default: image)
media.captionstring — Didascalia opzionale (max 1000 char)
media.filenamestring — Nome file (per document)

attachments[] (array, max 5)

Allegati per il canale email. Fornire tramite URL o contenuto base64.

attachments[].urlstring — URL del file (alternativo a content)
attachments[].contentstring — Contenuto base64 (alternativo a url)
attachments[].filenamestring req — Nome del file
attachments[].mime_typestring — MIME type (opzionale)

Comportamento per canale

  • WhatsApp — Il media appare come header image del template (richiede template con header IMAGE approvato su Meta).
  • Telegram — Inviato come foto separata prima del messaggio di testo.
  • Email — Aggiunto automaticamente come allegato al messaggio.
WhatsApp con foto recensione
{
  "recipient": { "whatsapp_phone": "+393331234567" },
  "template": "review_approval",
  "channel": "whatsapp",
  "vars": { "1": "Mario Rossi", "2": "5 stelle", "3": "Ottimo servizio!" },
  "media": {
    "url": "https://example.com/photo.jpg",
    "type": "image"
  }
}
Email con allegato PDF
{
  "recipient": { "email": "[email protected]" },
  "template": "report_monthly",
  "channel": "email",
  "attachments": [
    {
      "url": "https://sito.it/reports/apr-2026.pdf",
      "filename": "report-aprile.pdf",
      "mime_type": "application/pdf"
    }
  ]
}
GET /api/notifyhub/v1/usage

Ritorna i consumi del workspace nel periodo di fatturazione corrente. Auth via Bearer API key.

Campi risposta

messages.usedMessaggi inviati nel periodo
messages.limitLimite del piano (null = illimitato)
messages.remainingMessaggi residui
whatsapp.usedWhatsApp consumati (bundle separato)
period_start/endDate del periodo corrente
Response — 200 OK
{
  "plan_slug": "nh-pro",
  "plan_name": "Pro",
  "messages": { "used": 1234, "limit": 10000, "remaining": 8766, "percentage": 12 },
  "whatsapp": { "used": 45, "limit": null },
  "period_start": "2026-04-01",
  "period_end": "2026-04-30"
}
GET /api/notifyhub/v1/health

Endpoint pubblico (no auth). Per monitoring uptime.

Campi risposta

statushealthy | degraded (coda > 100)
workeractive | stale (fermo con coda piena)
queue_depthMessaggi in attesa
last_hourContatori sent/delivered/failed ultima ora
Response — 200 OK
{
  "status": "healthy",
  "worker": "active",
  "queue_depth": 0,
  "last_hour": { "sent": 12, "delivered": 11, "failed": 1 },
  "timestamp": "2026-04-29T20:38:52+00:00"
}

Webhook subscriptions

Registra endpoint HTTP per ricevere callback in tempo reale quando lo stato di una delivery cambia.

Eventi disponibili

delivery.queuedMessaggio accodato
delivery.sentInviato al provider
delivery.deliveredConsegnato al destinatario
delivery.readLetto (WhatsApp blue ticks)
delivery.failedInvio fallito definitivamente
delivery.action_takenUtente ha cliccato un bottone

Verifica firma

Ogni POST contiene l'header X-NotifyHub-Signature con HMAC-SHA256 del body firmato con il tuo whsec_* secret. Rifiuta richieste con firma non valida.

Auto-disable

Dopo 10 fallimenti consecutivi (timeout o HTTP non-2xx) l'endpoint viene disattivato automaticamente. Riattivabile dal portale.

Payload webhook
POST https://tuo-sito.it/webhook/notifyhub
X-NotifyHub-Signature: a1b2c3d4e5...
X-NotifyHub-Event: delivery.delivered
Content-Type: application/json

{
  "event": "delivery.delivered",
  "timestamp": "2026-04-29T15:30:00+00:00",
  "delivery": {
    "id": 12345,
    "status": "delivered",
    "recipient_id": 1,
    "channel": "whatsapp",
    "template_slug": "review_request",
    "external_id": "wamid.abc123",
    "error_message": null,
    "created_at": "2026-04-29T15:29:55+00:00"
  }
}
Verifica firma (PHP)
$body = file_get_contents('php://input');
$sig  = $_SERVER['HTTP_X_NOTIFYHUB_SIGNATURE'];
$expected = hash_hmac('sha256', $body, $secret);

if (!hash_equals($expected, $sig)) {
    http_response_code(401);
    exit('Invalid signature');
}

Action callbacks

Quando l'utente clicca un bottone interattivo (WhatsApp/Telegram), NotifyHub fa POST al tuo callback_url con firma HMAC.

Flusso

  1. Invii messaggio con actions[]
  2. NotifyHub genera URL firmati per ogni bottone
  3. Utente clicca → NotifyHub POST al tuo endpoint
  4. Verifica firma e processa l'azione

Replay protection

Controlla X-Notifyhub-Timestamp — rifiuta richieste oltre i 5 minuti dal tempo corrente.

Callback ricevuto dal tuo server
POST https://tuo-sito.it/api/reviews/456/moderate
X-Notifyhub-Signature: sha256=abc123...
X-Notifyhub-Timestamp: 1714000000
Content-Type: application/json

{
  "delivery_id": 12345,
  "action_slug": "approve",
  "payload": { "review_id": 456 },
  "actor": { "name": "Mario", "channel": "whatsapp" }
}

Verifica firma (PHP)

Esempio verifica HMAC
$body = file_get_contents('php://input');
$signature = $_SERVER['HTTP_X_NOTIFYHUB_SIGNATURE'] ?? '';
$timestamp = $_SERVER['HTTP_X_NOTIFYHUB_TIMESTAMP'] ?? '';

// Rifiuta richieste oltre 5 minuti
if (abs(time() - (int) $timestamp) > 300) {
    abort(403, 'Request too old');
}

$expected = 'sha256=' . hash_hmac('sha256', $body, $yourApiKey);

if (!hash_equals($expected, $signature)) {
    abort(403, 'Invalid signature');
}

$data = json_decode($body, true);
// Processa: $data['action_slug'], $data['payload'], etc.

Note per canale

WhatsApp

  • Template Meta obbligatorio fuori dalla finestra 24h.
  • I bottoni URL richiedono template approvato.
  • Le immagini funzionano solo con template che hanno header IMAGE.
  • Numero destinatario in formato E.164 (+39...).

Telegram

  • Il destinatario deve aver avviato il bot con /start.
  • I bottoni usano inline keyboard.
  • Le immagini vengono inviate come foto prima del testo.
  • Usa telegram_chat_id nel recipient.

Email

  • Allegati: max 5 per messaggio (URL o base64).
  • Header List-Unsubscribe aggiunto automaticamente.
  • Soggetto dal template o dal campo subject.
  • HTML generato automaticamente dal body testuale.

Messenger

  • Richiede Facebook Page connessa con permesso pages_messaging.
  • Finestra 24h standard messaging policy di Meta.
  • I bottoni usano quick-reply o URL button template.
  • Immagini e video supportati come allegati.

Discord

  • Usa webhook URL del canale Discord.
  • Supporta embed con colore, titolo e campi.
  • Max 2000 caratteri per messaggio.
  • Nessuna interattività (solo invio, non bottoni).

Slack

  • Webhook URL o Bot Token con chat:write.
  • Supporta Block Kit per layout ricchi.
  • Canale specificato nel recipient o nella source.
  • Nessuna interattività via action callback.

Codici di stato

202Notifica accettata e in coda
200Idempotency replay (stessa delivery)
401API key mancante o non valida
403API key revocata
404Template non trovato
422Validazione fallita (vedi details)
429Rate limit o quota mensile esaurita
Errore — 422
{
  "status": "validation_failed",
  "message": "The given data was invalid.",
  "details": {
    "recipient": ["The recipient field is required."],
    "template": ["The template field is required."]
  }
}
Errore — 429 quota (senza crediti)
{
  "error": "quota_exceeded",
  "message": "Monthly quota reached (500/500). Buy credits or upgrade.",
  "used": 500,
  "limit": 500,
  "hint": "credits_empty"
}
Risposta 202 con overage (crediti scalati)
{
  "status": "queued",
  "delivery_id": 1234,
  "channel": "whatsapp",
  "overage": {
    "credits_deducted": 3,
    "channel_cost": 3
  }
}

Se la quota mensile è esaurita ma hai crediti, il messaggio viene inviato e i crediti scalati. Costi: Telegram/Email 1 cr, WhatsApp 3 cr, SMS 10 cr.

Rate limiting

  • Per API key: default 100 req/ora, configurabile da /sources
  • Quota mensile: Free 500, Pro 10K, Agency 50K. WhatsApp ha bundle separato.
  • Idempotenza: campo idempotency_key — stessa chiave entro 1h ritorna lo stesso delivery con replayed: true
  • Retry automatici: 3 tentativi con backoff 30s → 2min → 10min
Rate limit headers
X-RateLimit-Limit: 100
X-RateLimit-Remaining: 42
Retry-After: 3600

Hai bisogno di aiuto?

STAG
https://notify.trovido.com