Guida per sviluppatori
Pubblicare campagne e inviare eventi
Integra le campagne sponsorizzate nella tua app in due passaggi: recupera le campagne che corrispondono al tuo contesto, poi invia un VIEW quando ne viene mostrata una e un CLICK quando viene toccata. Questa guida illustra l'intero ciclo con esempi pronti da copiare.
Salvata solo in questo browser (localStorage) e inviata con ogni richiesta “Prova” qui sotto. Non condividerla e non inserirla nel codice lato client.
Panoramica
L'integrazione è un ciclo breve. Chiedi al nostro server quali campagne mostrare per una determinata posizione e contesto di menu, le mostri e restituisci gli eventi così i budget vengono addebitati e i report restano accurati.
Passaggio 1
Recupera campagne
Cerca le campagne che corrispondono alle tue categorie di ristoranti, alla posizione e ai prodotti.
Passaggio 2
Invia un VIEW
Quando una campagna viene mostrata a un utente, invia un evento VIEW.
Passaggio 3
Invia un CLICK
Quando l'utente la tocca, invia un evento CLICK.
Cerca le categorie di ristoranti
La ricerca delle campagne è mirata ai ristoranti per categoria, quindi si parte da qui. Chiama GET /api/v1/restaurant-categories per sfogliare il catalogo, filtrando facoltativamente con search e traducendo i nomi con language. Conserva l<code>id</code> di ogni categoria che vuoi targetizzare: lo passerai come <code>restaurantCategoryIds</code> quando recuperi le campagne.
x-api-key.const params = new URLSearchParams({
search: "pizza",
language: "en",
limit: "20",
})
// Public endpoint — no API key needed.
const res = await fetch(
`https://adv.api.waithero.com/api/v1/restaurant-categories?${params}`,
)
const { data } = await res.json()
// data: [{ id, name, image }] — keep the ids you want to target.Parametri della query
| Campo | Tipo | Obbligatorio | Descrizione |
|---|---|---|---|
| search | string | No | Corrispondenza parziale e non sensibile alle maiuscole sul nome della categoria. Ometti per elencare tutto. |
| language | string | No | Codice lingua (es. "en", "it") usato per tradurre il nome di ogni categoria. Ometti per il nome predefinito. |
| page | integer | No | Numero di pagina (a partire da 1). Predefinito: 1. |
| limit | integer | No | Elementi per pagina, da 1 a 50. Predefinito: 20. |
La risposta è un elenco paginato. Leggi data per le categorie di questa pagina e total per sapere quante ne esistono in totale.
Risposta
| Campo | Tipo | Descrizione |
|---|---|---|
| data | [] | Le categorie della pagina corrente. |
| total | integer | Numero totale di categorie che corrispondono alla query, su tutte le pagine. |
| page | integer | Il numero di pagina restituito. |
| limit | integer | La dimensione della pagina applicata. |
RestaurantCategory — ogni elemento in data[]
| Campo | Tipo | Descrizione |
|---|---|---|
| id | integer | ID della categoria. Passalo in restaurantCategoryIds quando recuperi le campagne. |
| name | string | Nome della categoria, tradotto quando passi language. |
| image | string | null | URL dell'immagine della categoria, oppure null se non presente. |
Recupera campagne
Chiama POST /api/v1/client/campaigns/search con il contesto per cui vuoi gli annunci: la posizione, più le categorie di ristoranti e i prodotti attualmente visualizzati. Solo la posizione è obbligatoria — il resto restringe l'abbinamento. La risposta contiene le campagne da mostrare — conserva l<code>id</code> di ogni campagna, ti servirà per riportare gli eventi.
const res = await fetch(
"https://adv.api.waithero.com/api/v1/client/campaigns/search",
{
method: "POST",
headers: {
"Content-Type": "application/json",
"x-api-key": process.env.CAMPAIGNS_API_KEY,
},
body: JSON.stringify({
restaurantCategoryIds: [12, 34],
areaLat: 45.4642,
areaLon: 9.19,
products: [
{ referenceId: "sku-123", productName: "Margherita", categoryName: "Pizza" },
],
}),
},
)
const { campaigns } = await res.json()
// Render campaigns, and remember each campaign.id for event reporting.Corpo della richiesta
| Campo | Tipo | Obbligatorio | Descrizione |
|---|---|---|---|
| restaurantCategoryIds | integer[] | No | ID delle categorie di ristoranti da abbinare. Facoltativo — invia restaurantCategoryNames al suo posto, oppure nessuno dei due per cercare in tutte le categorie. |
| restaurantCategoryNames | string[] | No | Nomi delle categorie di ristoranti da abbinare (es. "Pizza"). Facoltativo — invia restaurantCategoryIds al suo posto, oppure nessuno dei due per cercare in tutte le categorie. |
| address | string | No | Indirizzo in testo libero dell'area per cui pubblichi gli annunci. Facoltativo se invii invece areaLat e areaLon. Se fornito, il server lo converte in coordinate e le restituisce nella risposta. |
| areaLat | number | No | Latitudine dell'area per cui pubblichi gli annunci. Facoltativa se invii invece address; forniscila con areaLon. |
| areaLon | number | No | Longitudine dell'area per cui pubblichi gli annunci. Facoltativa se invii invece address; forniscila con areaLat. |
| products | [] | No | I prodotti attualmente visualizzati — una voce per prodotto (struttura sotto). Facoltativo: senza prodotti possono corrispondere solo le campagne HOMEPAGE. |
restaurantCategoryIds, restaurantCategoryNames o entrambi. Gli ID corrispondono esattamente, quindi preferiscili quando li hai; i nomi vengono confrontati con il nostro catalogo. Non inviare nessuno dei due e la ricerca non viene filtrata per categoria.address in testo libero oppure con le coordinate areaLat + areaLon — invia almeno uno. Se invii un address, il server lo converte in coordinate e restituisce areaLat, areaLon e il tuo address risolti nel campo area della risposta.products è facoltativo, ma è ciò su cui vengono abbinate le campagne: le campagne SUGGESTION e HIGHLIGHT promuovono ciascuna un prodotto, quindi una ricerca che non invia prodotti può restituire solo campagne HOMEPAGE.products[] — ogni prodotto
| Campo | Tipo | Obbligatorio | Descrizione |
|---|---|---|---|
| referenceId | string | No | Facoltativo. Il tuo ID opaco del prodotto (es. uno SKU). Se impostato, viene restituito in matchedProductReferenceIds così sai quale campagna corrisponde a quale prodotto. |
| productName | string | Sì | Nome visualizzato del prodotto. |
| categoryName | string | Sì | Nome della categoria del prodotto. |
Ogni campagna nella risposta include il suo id, il type (HOMEPAGE, SUGGESTION o HIGHLIGHT), l<code>advisor</code>, leventuale promotedProduct e matchedProductReferenceIds — i referenceId che hai inviato e a cui questa campagna corrisponde, così sai esattamente dove posizionarla.
Risposta
| Campo | Tipo | Descrizione |
|---|---|---|
| area | La posizione su cui è stata eseguita la ricerca — un Area (campi sotto). Riporta le coordinate usate dal server, ricavate dal tuo address quando ne hai inviato uno. | |
| campaigns | [] | Le campagne da mostrare — ciascuna una Campaign (campi sotto). |
Area — la posizione di ricerca risolta
| Campo | Tipo | Descrizione |
|---|---|---|
| areaLat | number | Latitudine su cui è stata eseguita la ricerca. Ricavata da address se ne hai inviato uno, altrimenti l'areaLat che hai passato. |
| areaLon | number | Longitudine su cui è stata eseguita la ricerca. Ricavata da address se ne hai inviato uno, altrimenti l'areaLon che hai passato. |
| address | string | null | L'address che hai inviato, restituito, oppure null se hai cercato per coordinate. |
Risposta — ogni elemento di campaigns[]
| Campo | Tipo | Descrizione |
|---|---|---|
| id | integer | ID della campagna. Conservalo — lo invii come idAdCampaign quando registri gli eventi. |
| type | "HOMEPAGE" | "SUGGESTION" | "HIGHLIGHT" | Dove mostrare la campagna. |
| status | "ACTIVE" | Lo stato del ciclo di vita della campagna. La ricerca restituisce solo campagne attualmente in erogazione, quindi qui è sempre ACTIVE. |
| title | string | Titolo visualizzato. Presente per le campagne HOMEPAGE; assente per le altre. |
| dateStart | string | null | Inizio della finestra di pubblicazione (ISO 8601), oppure null. |
| dateEnd | string | null | Fine della finestra di pubblicazione (ISO 8601), oppure null. |
| advisor | L'inserzionista dietro la campagna — un Advisor (campi sotto). | |
| masterProducts | [] | I prodotti presentati dalla campagna — ciascuno un MasterProduct (campi sotto). |
| promotedProduct | | null | L'unico prodotto promosso per le campagne SUGGESTION e HIGHLIGHT, oppure null per le altre — un MasterProduct (campi sotto). |
| matchedProductReferenceIds | string[] | I referenceId che hai inviato e che questa campagna ha abbinato ai master product di Waithero. |
| homepage | | null | Il banner homepage mostrato per le campagne HOMEPAGE, oppure null per le altre — un HomepageContent (campi sotto). |
Advisor — l'inserzionista dietro la campagna
| Campo | Tipo | Descrizione |
|---|---|---|
| id | integer | L'ID dell'inserzionista. |
| name | string | Il nome visualizzato dell'inserzionista. |
| logo | string | null | URL del logo dell'inserzionista, oppure null se assente. |
| link | string | L'URL di destinazione dell'inserzionista (es. il suo negozio). |
Cosa sono i master product?
I master product sono il catalogo condiviso di Waithero degli articoli promossi dalle campagne. I products che invii descrivono il tuo menu; il nostro server abbina ciascuno a un master product per nome e categoria, e restituisce una campagna quando i suoi master product coincidono con i prodotti che hai inviato. I master product abbinati tornano in masterProducts e promotedProduct.
MasterProduct — usato da masterProducts[] e promotedProduct
| Campo | Tipo | Descrizione |
|---|---|---|
| id | integer | L'ID del prodotto. |
| name | string | Il nome visualizzato del prodotto. |
| categoryName | string | Il nome della categoria del prodotto. Assente quando non è determinabile. |
HomepageContent — contenuto del banner homepage
| Campo | Tipo | Descrizione |
|---|---|---|
| link | string | URL di destinazione aperto al tocco del banner. |
| title | string | Titolo del banner. |
| description | string | Testo di supporto del banner. Assente quando non impostato. |
| image | string | URL dell'immagine del banner. Assente quando non impostato. |
Invia eventi VIEW e CLICK
Invia un evento a POST /api/v1/events con l<code>idAdCampaign</code> della campagna e un <code>eventType</code> pari a <code>VIEW</code> o <code>CLICK</code>. Invia un <code>VIEW</code> nel momento in cui una campagna diventa visibile e un <code>CLICK</code> quando lutente la tocca.
async function reportEvent(idAdCampaign, eventType) {
await fetch("https://adv.api.waithero.com/api/v1/events", {
method: "POST",
headers: {
"Content-Type": "application/json",
"x-api-key": process.env.CAMPAIGNS_API_KEY,
},
body: JSON.stringify({ idAdCampaign, eventType }),
})
}
// When the ad is shown:
reportEvent(42, "VIEW")
// When the user taps it:
reportEvent(42, "CLICK")Corpo della richiesta
| Campo | Tipo | Obbligatorio | Descrizione |
|---|---|---|---|
| idAdCampaign | integer | Sì | ID della campagna a cui si riferisce l'evento (l'id ottenuto dalla ricerca). |
| eventType | "VIEW" | "CLICK" | Sì | L'evento che stai registrando. |
| idUser | integer | No | ID facoltativo dell'utente finale, per l'attribuzione. |
Una chiamata riuscita restituisce l'evento registrato con l<code>amount</code> addebitato e il nuovo <code>remainingBudget</code> della campagna.
Risposta
| Campo | Tipo | Descrizione |
|---|---|---|
| id | integer | ID dell'evento registrato. |
| idAdCampaign | integer | ID della campagna a cui si riferisce l'evento (l'id ottenuto dalla ricerca). |
| eventType | "VIEW" | "CLICK" | L'evento che stai registrando. |
| amount | number | Importo addebitato alla campagna per questo evento. |
| remainingBudget | number | Budget residuo della campagna dopo l'addebito. |
Inviare più campagne insieme
Se hai mostrato più campagne in una sola vista, inviale insieme con POST /api/v1/events/batch — stesso eventType e un elenco di ID campagna.
curl -X POST "https://adv.api.waithero.com/api/v1/events/batch" \
-H "Content-Type: application/json" \
-H "x-api-key: YOUR_API_KEY" \
-d '{ "idAdCampaigns": [42, 43], "eventType": "VIEW" }'Corpo della richiesta batch
| Campo | Tipo | Obbligatorio | Descrizione |
|---|---|---|---|
| idAdCampaigns | integer[] | Sì | ID delle campagne per cui registrare lo stesso evento. Almeno uno. |
| eventType | "VIEW" | "CLICK" | Sì | L'evento che stai registrando. |
| idUser | integer | No | ID facoltativo dell'utente finale, per l'attribuzione. |
Risposta batch — ogni elemento di results[]
| Campo | Tipo | Descrizione |
|---|---|---|
| idAdCampaign | integer | La campagna a cui si riferisce questo risultato. Sempre presente. |
| status | "ok" | "error" | ok se l'evento è stato registrato per quella campagna, altrimenti error. |
| id | integer | ID dell'evento registrato. Presente solo quando status è ok. |
| eventType | "VIEW" | "CLICK" | L'evento registrato. Presente solo quando status è ok. |
| amount | number | Importo addebitato alla campagna. Presente solo quando status è ok. |
| remainingBudget | number | Il budget residuo della campagna dopo l'addebito. Presente solo quando status è ok. |
| message | string | Dettaglio dell'errore quando status è error. |
results per campagna; ogni voce è ok o error, così una campagna in errore non blocca mai le altre.Esempio completo
Ecco l'intero ciclo di vita in un unico posto: risolvi le categorie che servi, recupera le campagne corrispondenti, segnala un VIEW quando ciascuna viene mostrata e un CLICK quando viene toccata.
Passo 1
Risolvi le categorie
Elenca le categorie di ristoranti e raccogli gli id che vuoi targetizzare.
Passo 2
Recupera le campagne
Cerca con quegli ID di categoria, la posizione e i prodotti in vista.
Passo 3
Segnala VIEW
Invia un VIEW per ogni campagna nel momento in cui diventa visibile.
Passo 4
Segnala CLICK
Invia un CLICK quando l'utente tocca una campagna.
const BASE = "https://adv.api.waithero.com/api/v1"
const headers = {
"Content-Type": "application/json",
"x-api-key": process.env.CAMPAIGNS_API_KEY,
}
// 1. Resolve the category IDs you want to target.
const categories = await fetch(
`${BASE}/restaurant-categories?search=pizza`,
{ headers },
).then((r) => r.json())
const restaurantCategoryIds = categories.data.map((c) => c.id)
// 2. Ask which campaigns to show for this context.
const { campaigns } = await fetch(`${BASE}/client/campaigns/search`, {
method: "POST",
headers,
body: JSON.stringify({
restaurantCategoryIds,
areaLat: 45.4642,
areaLon: 9.19,
products: [
{ referenceId: "sku-123", productName: "Margherita", categoryName: "Pizza" },
],
}),
}).then((r) => r.json())
// 3. Render each campaign, then report a VIEW when it becomes visible.
for (const campaign of campaigns) {
render(campaign) // your UI — place it using campaign.matchedProductReferenceIds
await fetch(`${BASE}/events`, {
method: "POST",
headers,
body: JSON.stringify({ idAdCampaign: campaign.id, eventType: "VIEW" }),
})
}
// 4. Report a CLICK when the user taps a campaign.
function onCampaignClick(campaign) {
return fetch(`${BASE}/events`, {
method: "POST",
headers,
body: JSON.stringify({ idAdCampaign: campaign.id, eventType: "CLICK" }),
})
}matchedProductReferenceIds per posizionare ogni campagna accanto al prodotto esatto che ha corrisposto, e conserva ogni campaign.id: è l<code>idAdCampaign</code> con cui segnali gli eventi.Errori e budget
La registrazione di un evento addebita il budget residuo della campagna (il suo costPerView o costPerClick). Quando il budget di una campagna raggiunge zero, viene terminata automaticamente e smette di comparire nelle ricerche successive. Gestisci queste risposte quando invii gli eventi:
| Stato | Significato |
|---|---|
| 400 | La campagna non è attiva, non è ancora iniziata, è terminata oppure il tipo di evento non è supportato. |
| 401 | Chiave API mancante o non valida. |
| 402 | Budget residuo insufficiente — la campagna non può più essere addebitata. |
| 404 | Campagna non trovata. |