API ed esempi di integrazione

Willy Push Engine – Push normali e push cifrate (E2EE). Documentazione completa, file da mettere nel sito, esempi in cURL, JavaScript, PHP e Python.

Base URL API: — · Come integrare le push criptate sul tuo sito

Riassunto

Base URL e autenticazione

Sostituisci BASE con l'URL della tua installazione (es. https://TUO_SITO.it/api-push). Per le API protette invia gli header:

X-Willy-App-Id: IL_TUO_APP_ID + X-Willy-App-Key: LA_TUA_APP_KEY — autenticazione tramite appId + appKey negli header HTTP
Dominio gestito lato server: il client non invia mai un parametro domain. Il server ricava il dominio dall’appId nel proprio database e valida l’header Origin del browser. Nessun dato di dominio viene inviato dal client.

Content-Type per POST: Content-Type: application/json.

Nota: Il server ricava il dominio dall'app_id. Non inviare mai un dominio dal client.

Sicurezza e tenant: il server ricava il dominio dall'appId nel proprio database. L'header Origin del browser viene validato automaticamente. Ogni richiesta deve includere gli header X-Willy-App-Id e X-Willy-App-Key; il server associa l'appId al dominio corretto senza che il client invii alcun parametro di dominio. Per dettagli su rate limit e audit vedi api-push/SECURITY.md.

Elenco API

Tutte le API in api/ richiedono gli header X-Willy-App-Id e X-Willy-App-Key. Il server ricava il dominio dall'appId. Content-Type per POST: application/json.

MetodoEndpointAuthDescrizione
GETget-firebase-config.phpNoConfig Firebase Web (apiKey, projectId, vapidKey, …) per il client.
GETapi/firebase_check.phpAdminVerifica service account (richiede X-API-TOKEN = WILLY_ADMIN_TOKEN).
POSTapi/register.phpSìRegistra/aggiorna device: push_token obbligatorio; app_id e app_key negli header. Opz. platform, device_type, business: public_key, firebase_project_id.
POSTapi/unregister.phpSìDisiscrivi device (push_token, reason?).
POSTapi/heartbeat.phpSìAggiorna last_seen_at (push_token). Debounce 5 min.
GETapi/list.php?app_id=...&limit=100&offset=0&platform=...&device_type=...&role=...SìLista device attivi (token_hash, no token completo). limit max 200, paginazione con offset.
GETapi/list_encrypted.php?app_id=...SìLista device con public_key (per push cifrate).
GETapi/stats.php?app_id=...SìStatistiche dominio (total_push_sent, total_push_failed, total_active_devices, …).
POSTapi/send.phpSìInvio singolo: title, body, url?, target? (token o filtri), data?, icon?, image?, tag?.
POSTapi/send_bulk.phpSìInvio multiplo: title, body, url?, target?, limit? (max server-side 2000).
POSTapi/send_encrypted.phpSìInvio cifrato: title, body, url?, icon?, badge?, image?, tag?, target?, limit?; oppure encrypted_messages[] (E2EE puro).
POSTapi/delete_device.phpSìRimuovi device (token_hash).
POSTapi/delete_encrypted_device.phpSìRimuovi device dalla lista push cifrate (token_hash).
GETapi/campaign_list.php?app_id=...SìLista campagne.
POSTapi/campaign_create.phpSìCrea campagna.
POSTapi/campaign_send.phpSìInvia campagna.
GETapi/campaign_stats.php?app_id=...&campaign_id=...SìStatistiche campagna.
POSTapi/cleanup_invalid.phpSìPulizia log e statistiche.

File da mettere nel sito (integrazione push)

Esempi di chiamate

1. Config Firebase (GET)

cURL

curl -s "BASE/get-firebase-config.php"

JavaScript (fetch)

const config = await fetch(BASE + '/get-firebase-config.php').then(r => r.json());
if (config.error) throw new Error(config.error);
// config.apiKey, config.projectId, config.vapidKey, ...

PHP

$config = json_decode(file_get_contents('https://TUO_SITO.it/api-push/get-firebase-config.php'), true);
if (!empty($config['error'])) throw new Exception($config['error']);

Python

import requests
r = requests.get(BASE + '/get-firebase-config.php')
config = r.json()
if 'error' in config:
    raise ValueError(config['error'])

2. Registrazione device (POST) – push normale

cURL

curl -X POST "BASE/api/register.php" \
  -H "Content-Type: application/json" \
  -H "X-Willy-App-Id: IL_TUO_APP_ID" -H "X-Willy-App-Key: LA_TUA_APP_KEY" \
  -d '{"push_token":"FCM_TOKEN_QUI","platform":"web","device_type":"web_browser","firebase_project_id":"tuo-project-id"}'

JavaScript (fetch)

await fetch(BASE + '/api/register.php', {
  method: 'POST',
  headers: { 'Content-Type': 'application/json', 'X-Willy-App-Id': appId, 'X-Willy-App-Key': appKey },
  body: JSON.stringify({
    push_token: fcmToken,
    platform: 'web',
    device_type: 'web_browser',
    firebase_project_id: firebase.app().options?.projectId || ''
  })
});

PHP

$payload = [
  'push_token' => $fcmToken,
  'platform' => 'web',
  'device_type' => 'web_browser',
  'firebase_project_id' => $projectId
];
$ch = curl_init($base . '/api/register.php');
curl_setopt_array($ch, [
  CURLOPT_POST => true,
  CURLOPT_POSTFIELDS => json_encode($payload),
  CURLOPT_HTTPHEADER => ['Content-Type: application/json', 'X-Willy-App-Id: ' . $appId, 'X-Willy-App-Key: ' . $appKey],
  CURLOPT_RETURNTRANSFER => true
]);
$res = curl_exec($ch);

Python

import requests
r = requests.post(BASE + '/api/register.php', json={
    'push_token': fcm_token,
    'platform': 'web',
    'device_type': 'web_browser',
    'firebase_project_id': project_id
}, headers={'X-Willy-App-Id': app_id, 'X-Willy-App-Key': app_key})
data = r.json()

3. Registrazione device con chiave pubblica (E2EE)

cURL

curl -X POST "BASE/api/register.php" \
  -H "Content-Type: application/json" \
  -H "X-Willy-App-Id: IL_TUO_APP_ID" -H "X-Willy-App-Key: LA_TUA_APP_KEY" \
  -d '{"push_token":"FCM_TOKEN","platform":"web","device_type":"web_browser","public_key":"BASE64URL_CHIAVE_PUBBLICA","firebase_project_id":"project-id"}'

JavaScript (fetch)

await fetch(BASE + '/api/register.php', {
  method: 'POST',
  headers: { 'Content-Type': 'application/json', 'X-Willy-App-Id': appId, 'X-Willy-App-Key': appKey },
  body: JSON.stringify({
    push_token: token,
    platform: 'web',
    device_type: 'web_browser',
    user_agent: navigator.userAgent,
    public_key: publicKeyBase64,
    firebase_project_id: window.firebase?.app()?.options?.projectId || ''
  })
});

4. Lista device (GET) – limit e offset per paginazione

cURL

curl -s -H "X-Willy-App-Id: IL_TUO_APP_ID" -H "X-Willy-App-Key: LA_TUA_APP_KEY" "BASE/api/list.php?app_id=IL_TUO_APP_ID&limit=100&offset=0"
curl -s -H "X-Willy-App-Id: IL_TUO_APP_ID" -H "X-Willy-App-Key: LA_TUA_APP_KEY" "BASE/api/list.php?app_id=IL_TUO_APP_ID&platform=web&device_type=web_browser"

JavaScript

const res = await fetch(BASE + '/api/list.php?app_id=' + encodeURIComponent(appId) + '&limit=100&offset=0', {
  headers: { 'X-Willy-App-Id': appId, 'X-Willy-App-Key': appKey }
});
const data = await res.json();
console.log(data.data.devices, data.data.count, data.data.total);

5. Lista device cifrati (GET) – richiede X-Willy-App-Id / X-Willy-App-Key

cURL

curl -s -H "X-Willy-App-Id: IL_TUO_APP_ID" -H "X-Willy-App-Key: LA_TUA_APP_KEY" "BASE/api/list_encrypted.php?app_id=IL_TUO_APP_ID"

PHP

$ch = curl_init($base . '/api/list_encrypted.php?app_id=' . urlencode($appId));
curl_setopt_array($ch, [
  CURLOPT_HTTPHEADER => ['X-Willy-App-Id: ' . $appId, 'X-Willy-App-Key: ' . $appKey],
  CURLOPT_RETURNTRANSFER => true
]);
$json = json_decode(curl_exec($ch), true);

6. Statistiche (GET)

cURL

curl -s -H "X-Willy-App-Id: IL_TUO_APP_ID" -H "X-Willy-App-Key: LA_TUA_APP_KEY" "BASE/api/stats.php?app_id=IL_TUO_APP_ID"

Risposta esempio

{
  "success": true,
  "data": {
    "domain": "tuodominio.it",
    "stats": {
      "total_registrations": 10,
      "total_active_devices": 8,
      "total_inactive_devices": 2,
      "total_push_sent": 150,
      "total_push_failed": 3,
      "total_invalidated_tokens": 1,
      "last_cleanup_at": "2025-03-14 12:00:00",
      "updated_at": "2025-03-14 12:00:00"
    }
  }
}

Nota: il campo domain nella risposta è ricavato dal server in base all'appId — non è un dato inviato dal client.

JavaScript

const res = await fetch(BASE + '/api/stats.php?app_id=' + encodeURIComponent(appId), {
  headers: { 'X-Willy-App-Id': appId, 'X-Willy-App-Key': appKey }
});
const data = await res.json();
const stats = data.data.stats;
console.log('Inviati:', stats.total_push_sent, 'Falliti:', stats.total_push_failed);

Python

r = requests.get(BASE + '/api/stats.php', params={'app_id': app_id}, headers={'X-Willy-App-Id': app_id, 'X-Willy-App-Key': app_key})
stats = r.json()['data']['stats']
print('Inviati:', stats['total_push_sent'])

7. Invio singolo (POST) – richiede X-Willy-App-Id / X-Willy-App-Key

cURL

curl -X POST "BASE/api/send.php" \
  -H "Content-Type: application/json" \
  -H "X-Willy-App-Id: IL_TUO_APP_ID" -H "X-Willy-App-Key: LA_TUA_APP_KEY" \
  -d '{"title":"Titolo","body":"Corpo messaggio","url":"/pagina"}'

JavaScript

await fetch(BASE + '/api/send.php', {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json',
    'X-Willy-App-Id': appId,
    'X-Willy-App-Key': appKey
  },
  body: JSON.stringify({
    title: 'Titolo',
    body: 'Corpo',
    url: '/pagina',
    target: {}
  })
});

PHP

$body = [
  'title' => 'Titolo',
  'body' => 'Corpo',
  'url' => '/pagina',
  'target' => []
];
$ch = curl_init($base . '/api/send.php');
curl_setopt_array($ch, [
  CURLOPT_POST => true,
  CURLOPT_POSTFIELDS => json_encode($body),
  CURLOPT_HTTPHEADER => ['Content-Type: application/json', 'X-Willy-App-Id: ' . $appId, 'X-Willy-App-Key: ' . $appKey],
  CURLOPT_RETURNTRANSFER => true
]);
$res = curl_exec($ch);

Python

requests.post(BASE + '/api/send.php', json={
    'title': 'Titolo', 'body': 'Corpo', 'url': '/pagina', 'target': {}
}, headers={'X-Willy-App-Id': app_id, 'X-Willy-App-Key': app_key})

8. Invio bulk (POST)

cURL

curl -X POST "BASE/api/send_bulk.php" \
  -H "Content-Type: application/json" \
  -H "X-Willy-App-Id: IL_TUO_APP_ID" -H "X-Willy-App-Key: LA_TUA_APP_KEY" \
  -d '{"title":"Bulk","body":"Messaggio","target":{},"limit":5000}'

PHP

$body = [
  'title' => 'Bulk',
  'body' => 'Messaggio',
  'target' => [],
  'limit' => 5000
];
$ch = curl_init($base . '/api/send_bulk.php');
curl_setopt_array($ch, [
  CURLOPT_POST => true,
  CURLOPT_POSTFIELDS => json_encode($body),
  CURLOPT_HTTPHEADER => ['Content-Type: application/json', 'X-Willy-App-Id: ' . $appId, 'X-Willy-App-Key: ' . $appKey],
  CURLOPT_RETURNTRANSFER => true
]);
$res = curl_exec($ch);

9. Invio cifrato E2EE (POST)

cURL

curl -X POST "BASE/api/send_encrypted.php" \
  -H "Content-Type: application/json" \
  -H "X-Willy-App-Id: IL_TUO_APP_ID" -H "X-Willy-App-Key: LA_TUA_APP_KEY" \
  -d '{"title":"Cifrato","body":"Solo questo device può decifrare.","url":"/","icon":"/logobase.png","target":{},"limit":5000}'

JavaScript

const res = await fetch(BASE + '/api/send_encrypted.php', {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json',
    'X-Willy-App-Id': appId,
    'X-Willy-App-Key': appKey
  },
  body: JSON.stringify({
    title: 'Cifrato',
    body: 'Solo questo device può decifrare.',
    url: '/',
    icon: '/logobase.png',
    target: {},
    limit: 5000
  })
});
const data = await res.json();
// data.data.sent, data.data.failed, data.data.invalidated, data.data.total

PHP

$body = [
  'title' => 'Cifrato',
  'body' => 'Solo questo device può decifrare.',
  'url' => '/',
  'icon' => '/logobase.png',
  'target' => [],
  'limit' => 5000
];
$ch = curl_init($base . '/api/send_encrypted.php');
curl_setopt_array($ch, [
  CURLOPT_POST => true,
  CURLOPT_POSTFIELDS => json_encode($body),
  CURLOPT_HTTPHEADER => ['Content-Type: application/json', 'X-Willy-App-Id: ' . $appId, 'X-Willy-App-Key: ' . $appKey],
  CURLOPT_RETURNTRANSFER => true
]);
$res = curl_exec($ch);

Python

r = requests.post(BASE + '/api/send_encrypted.php', json={
    'title': 'Cifrato',
    'body': 'Solo questo device può decifrare.',
    'url': '/', 'icon': '/logobase.png', 'target': {}, 'limit': 5000
}, headers={'X-Willy-App-Id': app_id, 'X-Willy-App-Key': app_key})
print(r.json()['data']['sent'], r.json()['data']['failed'])

10. Rimuovi device (POST) – token_hash da list o list_encrypted

cURL (normale)

curl -X POST "BASE/api/delete_device.php" \
  -H "Content-Type: application/json" \
  -H "X-Willy-App-Id: IL_TUO_APP_ID" -H "X-Willy-App-Key: LA_TUA_APP_KEY" \
  -d '{"token_hash":"abc123def456"}'

cURL (cifrato)

curl -X POST "BASE/api/delete_encrypted_device.php" \
  -H "Content-Type: application/json" \
  -H "X-Willy-App-Id: IL_TUO_APP_ID" -H "X-Willy-App-Key: LA_TUA_APP_KEY" \
  -d '{"token_hash":"abc123def456"}'

11. Disiscrivi (unregister) – con push_token

cURL

curl -X POST "BASE/api/unregister.php" \
  -H "Content-Type: application/json" \
  -H "X-Willy-App-Id: IL_TUO_APP_ID" -H "X-Willy-App-Key: LA_TUA_APP_KEY" \
  -d '{"push_token":"FCM_TOKEN","reason":"user"}'

12. Heartbeat

cURL

curl -X POST "BASE/api/heartbeat.php" \
  -H "Content-Type: application/json" \
  -H "X-Willy-App-Id: IL_TUO_APP_ID" -H "X-Willy-App-Key: LA_TUA_APP_KEY" \
  -d '{"push_token":"FCM_TOKEN"}'

13. Verifica Firebase (GET) – solo admin

cURL

curl -s -H "X-API-TOKEN: WILLY_ADMIN_TOKEN" "BASE/api/firebase_check.php"

Risposta standard

Tutte le API JSON restituiscono un oggetto con success (true/false), message, data (opzionale), request_id. In caso di errore: success: false, error_code, message.