API propform (REST)

Con l'API propform puoi gestire i tuoi moduli, gli invii, le statistiche, i modelli PDF, le carte intestate, i collaboratori e le impostazioni dell’account da applicazioni esterne, ad esempio dai tuoi strumenti, script o piattaforme di automazione come Zapier, Make o n8n.

> URL di base: https://propform.io/api/v1 > > Tutte le richieste e le risposte sono in formato JSON (Content-Type: application/json). I timestamp sono in formato ISO-8601 (ad es. 2026-07-07T08:30:00+00:00).

Guida rapida in 3 passaggi

  1. Creare una chiave API: Vai su Impostazioni → Chiavi API, clicca su Crea chiave API e copia la chiave visualizzata (formato pf_live_…). Per motivi di sicurezza, ti verrà mostrata solo questa volta.

  2. Verifica la connessione: sostituisci pf_live_DEIN_KEY e accedi a:

    curl https://propform.io/api/v1/me \
      -H "Authorization: Bearer pf_live_DEIN_KEY"
    

    Risposta:

    { "data": { "id": 42, "name": "Max Makler", "email": "max@makler.de" } }
    
  3. Prima richiesta effettiva – elenca i tuoi moduli:

   curl https://propform.io/api/v1/forms \
     -H "Authorization: Bearer pf_live_DEIN_KEY"

Autenticazione

L’API utilizza le chiavi API come token bearer. Ogni richiesta richiede la chiave nell’intestazione Authorization (Authorization: Bearer pf_live_…). In assenza di chiave o in caso di chiave non valida, l’API risponde con 401.

  • Puoi gestire le chiavi in Impostazioni → Chiavi API: crearle, assegnare loro un nome e revocarle in qualsiasi momento.
  • Una chiave revocata perde immediatamente l’accesso.
  • Per ogni account sono consentite al massimo 10 chiavi API.
  • Tratta la chiave come una password: essa consente l’accesso completo ai tuoi moduli e ai tuoi invii. Non rivelarla nel frontend del browser né in repository pubblici.

Limiti di frequenza

  • 60 richieste al minuto per chiave.
  • Inoltre, 300 richieste al minuto per indirizzo IP (protezione dal sovraccarico).

In caso di superamento del limite, l’API risponde con 429 Too Many Requests e un’intestazione Retry-After (secondi che mancano al prossimo tentativo consentito). Inserisci una breve pausa nelle automazioni quando visualizzi questo stato.

Formato della risposta, impaginazione ed errori

Le risposte andate a buon fine forniscono i dati nel campo data.

Gli elenchi sono impaginati. Gestiscili tramite per_page (impostazione predefinita 25, max. 100) e page. La risposta contiene inoltre links e meta:

{
  "data": [ { "id": 14, "internal_title": "Kontaktformular", "active": true } ],
  "links": { "first": "...", "last": "...", "prev": null, "next": "...?page=2" },
  "meta": { "current_page": 1, "per_page": 25, "total": 63, "last_page": 3 }
}

Gli errori hanno sempre un campo message; in caso di errori di convalida (422), i dettagli per ciascun campo sono riportati in errors:

{
  "message": "The url field is required.",
  "errors": { "url": ["The url field is required."] }
}
Stato Significato
200 / 201 Operazione riuscita (201 = nuovo record creato)
204 Operazione riuscita, nessun dato di risposta (ad es. cancellazione)
401 Chiave API assente o non valida
403 Il record non appartiene al tuo account (o funzione non inclusa nel piano tariffario)
404 Il record non esiste
409 Azione attualmente non possibile (ad es. onOffice non connesso)
422 Errore di convalida – Dettagli nel campo errors
429 Limite di rate raggiunto – Vedi Retry-After

Moduli

Metodo Endpoint Descrizione
GET /forms Elenca i propri moduli. Filtri: active (0/1), template (0/1), updated_since (data), folder (ID cartella o none), per_page, page
POST /forms Crea un nuovo modulo – è necessario solo internal_title, si avvia inattivo
GET /forms/{id} Leggi il modulo per intero (compresi i campi, l’e-mail e la configurazione di ChatGPT)
PATCH /forms/{id} Configurare il modulo – vengono modificati solo i campi inviati
POST /forms/{id}/copy Copia del modulo (compresi campi, regole, configurazione e-mail); la copia si avvia inattiva

Il modulo è una risorsa nell’API: puoi leggere e modificare le impostazioni relative all’invio delle e-mail e all’integrazione con ChatGPT direttamente sul modulo. I nomi dei campi corrispondono esattamente alle impostazioni nell’editor del modulo.

curl -X PATCH https://propform.io/api/v1/forms/123 \
  -H "Authorization: Bearer pf_live_DEIN_KEY" \
  -H "Content-Type: application/json" \
  -d '{"title": "Neuer Titel", "active": true}'

Caratteristiche importanti:

  • Semantica PATCH: i campi che non invii rimangono invariati. Puoi quindi modificare singole impostazioni in tutta sicurezza.
  • Attivare ("active": true) funziona solo finché non viene raggiunto il limite del tuo piano relativo ai moduli attivi (altrimenti 422). La disattivazione è sempre possibile.
  • I moduli non possono essere eliminati tramite API: questa operazione è possibile solo nella dashboard, per scelta.
  • Non sono modificabili, tra gli altri, slug, i contatori e i flag dei modelli. Le password (ad es. la password del modulo) sono modificabili, ma non compaiono mai nelle risposte.
  • L'elenco contiene tutti i tuoi moduli – compresi quelli condivisi come modello da copiare. Il campo di sola lettura form_template indica questa condivisione; con il filtro template=0/1 puoi filtrare in base ad essa.
  • Ogni risposta al modulo contiene l’url pubblico del modulo.

Impostazioni dei moduli utilizzate di frequente

Un modulo dispone di oltre 100 impostazioni (tutte quelle offerte anche dall’editor). Le più importanti:

Campo Significato
internal_title Nome interno (visibile solo nella dashboard)
title Titolo pubblico del modulo
custom_slug Parte descrittiva dell’URL (deve essere univoca nel tuo account)
active Modulo online (true) o offline (false)
description Testo introduttivo sopra il modulo
submit_button_label Etichetta del pulsante di invio
thankyou_headline / thankyou_text Titolo e testo della pagina di ringraziamento
redirect URL di reindirizzamento dopo l’invio (al posto della pagina di ringraziamento)
background_color / accent_color Colori di design (esadecimali, ad es. #0d6efd)
subject / body / receiver Oggetto, testo e destinatario dell’e-mail di conferma
send_email_via_onoffice Invia l’e-mail di conferma tramite onOffice

Puoi visualizzare l’oggetto completo in qualsiasi momento tramite GET /forms/{id}.

👉 Trovi l'elenco completo di tutti i campi e formati nella referenza dei campi API.

Campi

Metodo Endpoint Descrizione
GET /forms/{id}/fields Campi di un modulo (ordinati per posizione)
POST /forms/{id}/fields Crea campo – onoffice_module è obbligatorio, la posizione viene aggiunta automaticamente alla fine
GET /fields/{id} Leggere un singolo campo
PATCH /fields/{id} Modificare un campo (solo proprietà inviate)
DELETE /fields/{id} Elimina campo
PATCH /forms/{id}/fields/reorder Riordinare i campi – field_ids deve contenere tutti gli ID dei campi del modulo nell'ordine desiderato
curl -X POST https://propform.io/api/v1/forms/123/fields \
  -H "Authorization: Bearer pf_live_DEIN_KEY" \
  -H "Content-Type: application/json" \
  -d '{"onoffice_module": "address", "onoffice_field_name": "Vorname", "label": "Vorname", "required": true}'

Proprietà dei campi di uso frequente

Campo Significato
onoffice_module Modulo di destinazione: address, estate, task, calendar, agentsLog – oppure relationlist per il Campo di collegamento (obbligatorio al momento della creazione)
onoffice_field_name Nome del campo di destinazione in onOffice (vedi Configurazione dei campi onOffice)
label Etichetta del campo nel modulo
hint / placeholder Testo di aiuto o segnaposto nel campo di immissione
required Campo obbligatorio (true/false)
hidden / disabled / read_only Nascosto / disattivato / di sola lettura
default_value Valore predefinito
half_size Visualizza il campo a mezza larghezza sugli schermi di grandi dimensioni
datalist Valori di selezione come elenco separato da punti e virgola (Rot;Grün;Blau)
display_variant Variante di visualizzazione del campo (vedi sotto)
select_option_meta Dati aggiuntivi per chiave di selezione per le visualizzazioni a schede/icone/immagini (stringa JSON, vedi sotto)

Varianti di visualizzazione tramite API

Con display_variant controlli come un campo viene visualizzato nel modulo — il valore salvato e il collegamento a onOffice restano identici. null o vuoto = visualizzazione standard. Valori validi per tipo di campo:

Tipo di campo onOffice Valori
singleselect / user radio, buttons, cards, icons, images, likert
multiselect checkboxes, chips, icons, images
boolean checkbox, yesno
integer slider, stars, nps
float slider, stars, nps
date calendar
Criterio di ricerca da/a (integer/float nel modulo searchcriteria) rangeslider

I valori sconosciuti o non adatti al tipo di campo vengono ignorati durante il rendering (visualizzazione standard). Per cards, icons e images, select_option_meta fornisce i dati aggiuntivi per ogni valore chiave onOffice — una stringa JSON della forma:

{"haus": {"icon": "home", "description": "Indipendente o a schiera"}, "wohnung": {"image": "https://…/wohnung.jpg"}}

icon è il nome di un'icona del set integrato Tabler Icons (solo minuscole/cifre/trattini, ad es. home, building-estate, currency-euro), image un URL di immagine http(s) (idealmente dalla libreria multimediale), description un testo sotto il titolo dell'opzione (schede, riquadri con icone e immagini). Le icone compaiono sui riquadri con icone, davanti all'etichetta dell'opzione nel gruppo di pulsanti e sopra i pulsanti della scala Likert. Le voci vuote o sconosciute vengono ignorate. Per il cursore, valore minimo/massimo e intervallo provengono da min_value/max_value/step (anche per il rangeslider da/a); per la valutazione, max_value determina il numero di livelli (2–10, predefinito 5).

Opzioni di impostazione per visualizzazione (tutte opzionali; i valori non adatti vengono ignorati durante il rendering):

Proprietà Vale per Significato
variant_axis_labels slider Etichette personalizzate sotto il cursore, separate da punto e virgola (ad es. economico;medio;caro); ha priorità su datalist
slider_allow_input slider, rangeslider true = il valore può anche essere digitato in un campo numerico
range_default_von / range_default_bis rangeslider + da/a standard Preimposta «da» e «a» separatamente (ha priorità su default_value, che riempie entrambi i lati allo stesso modo); sul cursore i valori preimpostati — a differenza delle manopole non toccate — vengono inviati
scale_label_left / scale_label_right stars, nps, likert Etichette degli estremi della scala
rating_symbol stars stars (predefinito), hearts, smileys (scala dell'umore, 2–5 livelli; oltre, fallback sulle stelle) o custom (icona personalizzata tramite rating_custom_icon)
rating_custom_icon stars Nome di icona Tabler per rating_symbol: "custom" (non valido/vuoto ⇒ stelle)
variant_tile_size cards, icons, images compact o large (vuoto = normale)
variant_image_ratio images square o 16x9 (vuoto = 4:3)
variant_image_fit images contain = adattare l'immagine intera (vuoto = riempire/ritagliare)
multiselect_min_selected / multiselect_max_selected visualizzazioni a selezione multipla Numero minimo/massimo di opzioni selezionabili (imposto lato browser)
calendar_date_mode calendar future (solo da oggi) o past (solo fino a oggi)
calendar_min_date / calendar_max_date calendar Data minima/massima fissa (YYYY-MM-DD); ha priorità su calendar_date_mode
calendar_disable_weekends calendar true = bloccare i fine settimana
yesno_yes_label / yesno_no_label yesno Etichette personalizzate dei pulsanti (vuoto = «Sì»/«No» nella lingua dell'account)
yesno_icons yesno true = icone sui pulsanti (predefinito spunta/croce)
yesno_yes_icon / yesno_no_icon yesno Icone Tabler personalizzate al posto di spunta/croce (hanno effetto solo con yesno_icons: true)
variant_columns radio, checkboxes Colonne dell'elenco: 13 (su mobile sempre una sola colonna)
variant_centered pulsanti/riquadri/chips/yesno/stars/nps/calendar/cursori true = centrato invece che allineato a sinistra (nei cursori, il campo di inserimento)
variant_icon_color icons, yesno, buttons, likert Colore icona personalizzato in esadecimale (#RRGGBB); vuoto = eredita il colore del testo

Nota selezione multipla: Le visualizzazioni a riquadri/elenchi mostrano le opzioni in modo piatto; lì non esiste l'automatismo padre-figlio. Il multiselect_mode salvato non viene modificato quando imposti una variante di visualizzazione e torna ad avere effetto non appena ripristini la visualizzazione standard.

Nota cursore: Il cursore semplice — come il cursore da/a — invia un valore solo dopo un'interazione; un cursore non toccato si comporta come un campo vuoto.

Nota: Il range-slider precedente tramite special_field: "range" continua a funzionare invariato (legacy). Per i nuovi cursori consigliamo display_variant: "slider" — se un campo ha entrambi impostati, vince display_variant.

Campo di collegamento tramite API

Il Campo di collegamento (visualizzare, collegare, creare, modificare e scollegare i record onOffice collegati) si crea con onoffice_module: "relationlist". Il tipo di relazione è composto da relation_type (ad es. estate:address:owner) più relation_anchor_module (estate o address – quale lato è il record caricato nel modulo); a questi si aggiungono gli interruttori relation_show_existing, relation_allow_search, relation_allow_create, relation_allow_edit, relation_allow_remove, il controllo dei duplicati (relation_duplicate_check + relation_duplicate_check_fields) e l'elenco degli elementi del mini-modulo relation_subform_elements (array di {type: "field", name, required, half} oppure elementi di layout {type: "headline"|"description"|"dividingline"|"collapsible", text}). Per la creazione ci sono inoltre relation_advisor_mode (anchor = usa il referente del record caricato, fixed = utente fisso da relation_advisor_user_id) e relation_auto_open_create (apri automaticamente il mini-modulo quando non sono visualizzati collegamenti). Tutti i valori e i limiti sono riportati nella referenza dei campi.

curl -X POST https://propform.io/api/v1/forms/123/fields \
  -H "Authorization: Bearer pf_live_DEIN_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "onoffice_module": "relationlist",
    "relation_type": "estate:address:owner",
    "relation_anchor_module": "estate",
    "label": "Proprietario",
    "relation_show_existing": true,
    "relation_allow_create": true,
    "relation_subform_elements": [
      {"type": "field", "name": "Vorname", "half": true},
      {"type": "field", "name": "Name", "required": true, "half": true},
      {"type": "field", "name": "Email", "required": true}
    ]
  }'

Ordinamento: nel caso di reorder, l’ordine nell’array field_ids determina la nuova posizione. L'array deve contenere esattamente tutti gli ID dei campi del modulo (se ne manca uno o ne è presente uno estraneo → 422):

curl -X PATCH https://propform.io/api/v1/forms/123/fields/reorder \
  -H "Authorization: Bearer pf_live_DEIN_KEY" \
  -H "Content-Type: application/json" \
  -d '{"field_ids": [4712, 4711, 4713]}'

Configurazione dei campi di onOffice

GET /onoffice/fields fornisce la configurazione dei campi del tuo account onOffice – per ogni modulo (address, estate, task, calendar, agentsLog, file, project) tutti i campi con etichetta, tipo, lunghezza e valori consentiti. In questo modo puoi trovare i valori validi per onoffice_field_name. Con ?module=address puoi filtrare per un modulo.

{
  "data": {
    "address": {
      "Vorname":  { "label": "Vorname", "type": "varchar", "length": 50, "default": null, "permittedvalues": null },
      "Anrede":   { "label": "Anrede",  "type": "singleselect", "length": null, "default": null,
                    "permittedvalues": { "HERR": "Herr", "FRAU": "Frau" } }
    }
  }
}

> È necessario che la connessione all’API di onOffice funzioni correttamente; in caso contrario, l’endpoint risponde con 409.

POST /onoffice/fields/refresh ricarica la configurazione dei campi da onOffice, ad esempio dopo aver creato un campo o modificato i valori consentiti in onOffice. L'aggiornamento viene eseguito in background: la risposta è 202 con data.status = pending; pochi secondi dopo, GET /onoffice/fields restituisce il nuovo stato. Entrambi gli endpoint restituiscono un oggetto meta con la data dell'ultima generazione (updated_at), la lingua delle etichette dei campi (locale) e refresh_pending (true finché è ancora in corso un aggiornamento avviato tramite l'API). Senza questa chiamata, propform aggiorna la configurazione una volta al giorno e ogni volta che viene aperta la panoramica dei moduli. Limite: 120 chiamate all'ora.

{
  "message": "…",
  "data": { "status": "pending", "already_pending": false },
  "meta": { "updated_at": "2026-09-08T06:12:41+02:00", "locale": "de", "refresh_pending": true, "last_refresh_failed": false }
}

Condizioni (regole)

Metodo Endpoint Descrizione
GET /forms/{id}/rules Regole di un modulo (ordinate per priorità)
PUT /forms/{id}/rules Sostituisci tutte le regole del modulo (array vuoto = elimina tutte)

L'endpoint di scrittura funziona come l'editor delle condizioni nella dashboard: tutte le regole vengono sempre salvate in una volta sola (non è possibile aggiungerne una singola alla volta). Quindi, leggi prima con GET, modifica l’elenco e invialo per intero tramite PUT. Una regola è costituita da clauses (condizioni «Se», collegate a mode = all/any) e actions (azioni "allora"):

curl -X PUT https://propform.io/api/v1/forms/123/rules \
  -H "Authorization: Bearer pf_live_DEIN_KEY" \
  -H "Content-Type: application/json" \
  -d '{"rules": [{
    "title": "Zusatzfeld einblenden",
    "mode": "all", "priority": 10, "is_enabled": 1, "run_on_init": 1,
    "clauses": [{"when_field_id": "4711", "operator": "equals", "value": "Ja"}],
    "actions": [{"order": 10, "action": "show", "target_field_id": "4712"}]
  }]}'
  • Operatori: equals, not_equals, empty, not_empty, starts_with, not_starts_with, contains, not_contains
  • Azioni: show, hide, enable, disable, require, optional, hide_option, unhide_option, set_value, set_label, make_readonly, unmake_readonly, enable_rules_by_tag, disable_rules_by_tag
  • Come destinazione sono consentiti, oltre agli ID dei campi, anche destinazioni virtuali: __form_title, __form_description, __submit, __submit_spinner, __multistep_back, __multistep_next (in questo caso solo set_label, show, hide; per lo spinner solo set_label).
  • Tutti gli ID dei campi a cui si fa riferimento devono appartenere al modulo.
  • Limiti per richiesta: max. 200 regole, 50 clausole/azioni ciascuna.
  • Le condizioni sono disponibili, come nella dashboard, solo a partire dal piano con 10 moduli attivi (altrimenti 403).

Per i dettagli sul funzionamento delle regole, consulta Condizioni, regole e calcoli.

Invii e statistiche

Metodo Endpoint Descrizione
GET /forms/{id}/submissions Invii di un modulo. Filtro: created_after, created_before, sort (asc/desc), per_page
GET /submissions/{id} Invio singolo
GET /forms/{id}/stats Visualizzazioni, invii e tasso di conversione; opzionale from/to (data)

Il contenuto di un invio (content) è raggruppato in base ai moduli di onOffice:

{
  "data": {
    "id": 456, "form_id": 123, "index": 42,
    "content": { "address": { "Vorname": "Max", "Name": "Mustermann" }, "estate": { "Id": "131" } },
    "ip_address": "203.0.113.7",
    "created_at": "2026-07-07T08:30:00+00:00"
  }
}

Se nelle impostazioni sulla privacy hai disattivato il salvataggio dei dati degli invii, content è sempre null – l’invio viene comunque conteggiato nelle statistiche. La risposta stats contiene views, submissions e conversion_rate.

Webhook (nuovi invii in tempo reale)

Invece di interrogare regolarmente gli invii, puoi ricevere notifiche tramite webhook: propform invia immediatamente un POST al tuo URL ogni volta che viene effettuato un nuovo invio.

💡 Puoi gestire i webhook anche nel frontend, senza chiave API: nelle impostazioni alla voce «Webhooks» (per tutti i moduli) oppure direttamente nell'editor dei moduli alla voce «Webhook (Zapier, n8n, Make …)».

Metodo Endpoint Descrizione
GET /webhooks Elenca i tuoi abbonamenti ai webhook
POST /webhooks Crea un abbonamento: url (obbligatorio), form_id (facoltativo – se non specificato, l’abbonamento si applica a tutti i tuoi moduli), payload_template (facoltativo – vedi sotto), message_key (facoltativo – nome del campo del messaggio risolto, predefinito text)
DELETE /webhooks/{id} Elimina abbonamento

Requisiti per l’URL: deve essere accessibile pubblicamente e utilizzare http:// o https://. Gli indirizzi interni o locali (ad es. localhost, 127.0.0.1, IP di reti private) vengono rifiutati per motivi di sicurezza (422). Poiché i dati inviati vengono trasmessi, utilizza se possibile https://.

La risposta generata al momento della creazione contiene l’secret, con cui viene firmata ogni consegna (puoi recuperarlo in qualsiasi momento tramite GET /webhooks):

{
  "data": {
    "id": 7, "event": "submission.created",
    "url": "https://example.com/hook", "form_id": 123,
    "secret": "a1b2c3…", "payload_template": null, "message_key": "text", "is_active": true,
    "failure_count": 0, "last_success_at": null, "last_failure_at": null,
    "created_at": "2026-07-07T08:30:00+00:00"
  }
}

Ogni consegna contiene due intestazioni:

  • X-Propform-Event – il tipo di evento (attualmente sempre submission.created)
  • X-Propform-Signaturesha256=<HMAC-SHA256 des Request-Bodys mit deinem secret>

Esempio di payload:

{
  "event": "submission.created",
  "created_at": "2026-07-07T08:30:00+00:00",
  "form": { "id": 123, "slug": "aB3x…", "custom_slug": "kontakt", "title": "…", "internal_title": "…" },
  "submission": { "id": 456, "index": 42, "content": { "address": { "Vorname": "Max" } } },
  "onoffice_ids": { "address": { "ID": "789" } }
}

Testo libero / messaggio con macro onOffice (payload_template)

Facoltativamente, puoi salvare un modello di testo libero per ogni abbonamento. Dopo ogni invio del modulo, propform vi risolve le macro onOffice (sui record creati o modificati dall'invio) e include il risultato nel payload come campo di messaggio dedicato: ideale come messaggio pronto per Slack e simili, senza dover assemblare nulla nel flusso di lavoro ricevente. Il nome del campo è text per impostazione predefinita (vedi sotto).

Esempio di modello:

Nuova richiesta di _Vorname _Name per _objekttitel → _getEstateLink

nel payload diventa:

"text": "Nuova richiesta di Max Mustermann per Traumwohnung am Stadtpark → https://smart.onoffice.de/…"

Nome del campo del messaggio (message_key): Per impostazione predefinita il campo si chiama text: così Slack, Microsoft Teams e Google Chat interpretano il payload direttamente come messaggio. Discord si aspetta content. Per Zapier/n8n/Make il nome è libero. Puoi modificarlo per ogni webhook tramite il parametro message_key (nel frontend alla voce «Nome del campo del messaggio»).

Da tenere presente:

  • Funzionano le stesse macro dei modelli e-mail di propform (macro dei campi onOffice, _getAddressLink, _getEstateLink, …).
  • Una macro può essere risolta solo se l'invio dispone del contesto di record corrispondente (ad es. _objekttitel richiede un immobile collegato). Senza contesto, la macro resta non risolta nel testo.
  • I valori scritti solo dopo l'invio del webhook (caricamenti di file su onOffice, campi a selezione multipla scritti tramite «estendi») possono ancora mancare nel testo risolto.
  • Senza modello di testo libero non viene inviato alcun campo messaggio.
  • Massimo 5.000 caratteri.

Verifica della firma (consigliata)

Verifica la firma per assicurarti che la richiesta provenga effettivamente da propform. Calcola l’HMAC sul corpo della richiesta grezzo (non sul JSON analizzato) e confrontalo in tempo reale.

PHP:

$secret    = 'DEIN_WEBHOOK_SECRET';
$body      = file_get_contents('php://input');
$signature = $_SERVER['HTTP_X_PROPFORM_SIGNATURE'] ?? '';
$expected  = 'sha256=' . hash_hmac('sha256', $body, $secret);

if (!hash_equals($expected, $signature)) {
    http_response_code(403);
    exit('Ungültige Signatur');
}
// ab hier: $body ist echt – json_decode($body, true) verarbeiten

Node.js (Express):

const crypto = require('crypto');

// WICHTIG: express.raw() nutzen, damit der Body unverändert bleibt
app.post('/hook', express.raw({ type: 'application/json' }), (req, res) => {
  const secret   = process.env.PROPFORM_WEBHOOK_SECRET;
  const expected = 'sha256=' + crypto.createHmac('sha256', secret).update(req.body).digest('hex');
  const got      = req.header('X-Propform-Signature') || '';

  if (expected.length !== got.length ||
      !crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(got))) {
    return res.status(403).send('Ungültige Signatur');
  }
  const payload = JSON.parse(req.body.toString());
  res.sendStatus(200);
});

Consegna, ripetizione e limiti

  • Il tuo endpoint dovrebbe rispondere con uno stato 2xx. In caso di errori, propform tenta la consegna fino a 3 volte (con un intervallo di attesa).
  • Se un abbonamento fallisce 20 volte consecutive, viene disattivato automaticamente (is_active: false, visibile tramite GET /webhooks). A quel punto basta ricrearlo.
  • Per ogni account sono consentiti al massimo 20 abbonamenti webhook.
  • Gli allegati non fanno parte del payload.

Cartelle dei moduli

Le cartelle strutturano la panoramica dei moduli nella dashboard. Ogni modulo si trova al massimo in una cartella (form_folder_id, null = "Senza cartella"). Le cartelle possono avere un sottolivello (parent_id, al massimo due livelli). Da non confondere con i gruppi di moduli, che controllano l'autorizzazione alla copia.

Metodo Endpoint Descrizione
GET /form-folders Le tue cartelle con ID e numero di moduli
POST /form-folders Crea una cartella – è necessario name (max. 100 caratteri), opzionale parent_id (ID di una cartella principale → crea una sottocartella)
PATCH /form-folders/{id} Rinomina una cartella (name) e/o spostala (parent_id: ID di una cartella principale o null = cartella principale)
DELETE /form-folders/{id} Elimina una cartella – i moduli vengono conservati e tornano in "Senza cartella", le sottocartelle diventano cartelle principali

L'assegnazione si imposta nel PATCH del modulo tramite form_folder_id (ID di una propria cartella o null). Nell'elenco dei moduli puoi filtrare con ?folder=<id> o ?folder=none.

Gruppi di moduli

Metodo Endpoint Descrizione
GET /form-groups I tuoi gruppi con ID, flag copy e numero di moduli
POST /form-groups Crea un gruppo – è necessario solo name
PATCH /form-groups/{id} Modifica name e/o copy (possibilità di copiare i moduli di questo gruppo)
DELETE /form-groups/{id} Elimina un gruppo – i moduli restano e perdono solo l’assegnazione al gruppo

Puoi assegnare gli ID dei gruppi nel PATCH del modulo come form_groups (array).

Modelli PDF (generatore PDF)

propform può creare un proprio PDF da ogni invio (vedi Generatore PDF). Il modello di un modulo – elenco dei blocchi, design e impostazioni – lo leggi e lo scrivi completamente tramite l’API:

Metodo Endpoint Descrizione
GET /forms/{id}/pdf-template Leggi il modello (enabled, name, blocks, design, settings); se non ne esiste ancora uno, viene proposto automaticamente a partire dai campi del modulo
PATCH /forms/{id}/pdf-template Aggiornamento parziale – vengono modificate solo le parti inviate
POST /forms/{id}/pdf-template/regenerate Rigenera l’elenco dei blocchi dai campi attuali del modulo (design, impostazioni e stato attivo restano invariati)
GET /forms/{id}/pdf-template/preview Anteprima PDF reale con dati di esempio (application/pdf; max 10 richieste/minuto)
GET /account/pdf-design Leggi il preset di design valido per tutto l’account (design + settings)
PATCH /account/pdf-design Modifica il preset di design – i nuovi modelli dei moduli lo ereditano come punto di partenza
curl -X PATCH https://propform.io/api/v1/forms/123/pdf-template \
  -H "Authorization: Bearer pf_live_DEIN_KEY" \
  -H "Content-Type: application/json" \
  -d '{"enabled": true, "design": {"accent_color": "#0d6efd"}}'

Caratteristiche importanti:

  • La risposta è sempre lo stato normalizzato dopo il salvataggio – esattamente ciò che mostrerebbe anche l’editor dei modelli. Le chiavi sconosciute e i riferimenti a campi non validi vengono scartati silenziosamente: in caso di dubbio, confronta la risposta con ciò che hai inviato.
  • L’elenco dei blocchi è un’istantanea: i campi che aggiungi tramite API dopo la creazione del modello non vi vengono inseriti automaticamente. Usa regenerate (sostituisce le tue personalizzazioni dei blocchi!) oppure integra i blocchi in modo mirato tramite PATCH.
  • Se attivi settings.document_signing.enabled, propform provvede automaticamente a un blocco firma del documento nel modello (se non ne è già presente uno).

Riferimento del modello a blocchi

blocks è un array di blocchi. Ogni blocco ha type, un id assegnato automaticamente e proprietà specifiche del tipo (max. 300 blocchi per modello):

Tipo Proprietà
heading text (max. 500 caratteri), level (1, 2 o 3)
paragraph text (max. 5.000 caratteri)
field Singolo campo con valore: field_id (campo di questo modulo) oppure onoffice (riferimento, vedi sotto), label facoltativo
fieldlist Elenco etichetta-valore: field_ids = array di ID di campi e/o riferimenti onOffice
columns Due colonne: left e right sono ciascuno array di blocchi (un solo livello di profondità)
image url (http/https), width_percent (10–100), align (left/center/right), caption
divider style: line (linea) o space (spazio)
pagebreak Interruzione di pagina, nessuna proprietà
signature field_id = campo firma del modulo oppure null = firma del documento dopo l’invio; label; required (solo per la firma del documento)

Riferimenti onOffice: in field/fieldlist puoi riportare, al posto di un ID di campo, anche un campo del record onOffice collegato: {"module": "address", "name": "Telefon1", "label": "Telefono"} (moduli address ed estate; il campo deve esistere nella tua configurazione dei campi onOffice).

Esempio:

{
  "blocks": [
    {"type": "heading", "text": "Autocertificazione dell'inquilino", "level": 1},
    {"type": "paragraph", "text": "Dati dell'interessato:"},
    {"type": "fieldlist", "field_ids": [4711, 4712, {"module": "estate", "name": "objekttitel", "label": "Immobile"}]},
    {"type": "pagebreak"},
    {"type": "signature", "field_id": null, "label": "Luogo, data, firma", "required": true}
  ]
}

Chiavi di design (design): font, heading_font, font_size (7–14), text_color/heading_color/accent_color (esadecimali), logo_url, logo_position (left/center/right), logo_width_mm (10–90), label_column_width (20–60), header_text, footer_text, show_page_numbers, letterhead_id (vedi Carte intestate) e letterhead_margin_top/right/bottom/left (0–100 mm, null = margini della carta intestata).

Chiavi delle impostazioni (settings): empty_fields (hide/dash/blank), embed_uploaded_images, attach_uploaded_pdfs, embed_existing_files, embedded_image_size (small/column/full), document_signing.enabled.

Carte intestate

La tua carta intestata (PDF o immagine) viene posta come sfondo sotto ogni pagina del PDF generato – facoltativamente con un foglio successivo dedicato a partire dalla pagina 2:

Metodo Endpoint Descrizione
GET /letterheads Le tue carte intestate; con ?form_id= esattamente quelle selezionabili in questo modulo
POST /letterheads Carica (multipart/form-data, vedi sotto)
PATCH /letterheads/{id} Modifica letterhead_name e i margini
DELETE /letterheads/{id} Elimina – i modelli che la utilizzano tornano poi al layout standard

Parametri di caricamento (POST, multipart): letterhead_name (obbligatorio), letterhead_first (obbligatorio, PDF/PNG/JPG fino a 10 MB – il primo foglio), letterhead_continuation (facoltativo – il foglio successivo, stesso tipo di file), letterhead_margin_top/right/bottom/left (facoltativi, mm), form_id (facoltativo – in questo modo la carta intestata diventa privata del modulo; senza, finisce nella libreria dell’account).

curl -X POST https://propform.io/api/v1/letterheads \
  -H "Authorization: Bearer pf_live_DEIN_KEY" \
  -F "letterhead_name=Carta intestata aziendale" \
  -F "letterhead_first=@briefbogen.pdf" \
  -F "letterhead_continuation=@folgebogen.pdf"
  • I PDF crittografati o danneggiati vengono rifiutati con 422. Le immagini a bassa risoluzione vengono accettate, ma in tal caso la risposta contiene un avviso nel campo warning.
  • Assegnazione al modulo: imposta design.letterhead_id nel PATCH del modello PDF (le carte intestate dell’account ovunque, quelle private del modulo solo nel rispettivo modulo).

Impostazioni dell’account

Metodo Endpoint Descrizione
GET /account Leggi le impostazioni dell’account raggruppate
PATCH /account Aggiornamento parziale – vengono modificati solo i gruppi/campi inviati

La risposta è raggruppata secondo le sezioni delle impostazioni della dashboard:

Gruppo Contenuto Modificabile
profile name (nome del profilo/dell’azienda)
styling Tutti i valori predefiniti di design default_* per i nuovi moduli (colori, font, logo, aspetto delle schede …)
imprint Note legali standard: default_privacypolicy_url, default_imprint_url, default_homepage_url, da default_imprint_line_1 a _5
notifications Fino a 3 destinatari delle notifiche tecniche (notification_email_13) + notification_self
statistics save_submission_data, save_ips (privacy)
domain username (sottodominio), externaldomain, externaldomain_active ❌ solo lettura
staff_area enabled, session_days (1–90), seat_limit + active_members (solo lettura) parzialmente
curl -X PATCH https://propform.io/api/v1/account \
  -H "Authorization: Bearer pf_live_DEIN_KEY" \
  -H "Content-Type: application/json" \
  -d '{"styling": {"default_accent_color": "#0d6efd"}, "statistics": {"save_ips": false}}'
  • domain è di sola lettura per scelta: un cambio di sottodominio o dominio romperebbe tutti i link ai moduli già incorporati – questa operazione è possibile solo nella dashboard.
  • staff_area.enabled può essere attivato solo se il tuo piano include posti collaboratore (seat_limit > 0, altrimenti 422). seat_limit stesso deriva dal piano e non è modificabile.
  • Indirizzo e-mail (login), password, chiavi API, certificati e dati di abbonamento/pagamento non fanno parte dell’API.
  • Gli slot e-mail in notifications vengono salvati come elenco compatto: dopo il salvataggio le posizioni vuote vengono compattate.

Configurazione onOffice

Metodo Endpoint Descrizione
GET /account/onoffice Stato della connessione e impostazioni dell’integrazione onOffice
PATCH /account/onoffice Modifica le impostazioni (max. 5 richieste/minuto)

La risposta contiene tra l’altro api_type (normal/marketplace_api), connection_ok, credentials_set, email_onoffice_api_user (+ _is_functional), field_config_locale e reason_cancellation. Per motivi di sicurezza, il token e il secret dell’API non compaiono in nessuna risposta.

Modificabili tramite PATCH:

Campo Significato
api_type normal (credenziali API onOffice proprie) oppure marketplace_api (integrazione Marketplace; richiede un utente Marketplace collegato)
email_onoffice_api_user Identità e-mail dell’utente API come array (max 10) – ogni indirizzo viene verificato con un invio di prova
email_onoffice_marketplace_api_user idem per l’integrazione Marketplace
field_config_locale Lingua delle etichette dei campi onOffice (de, en, …) – la cache della configurazione dei campi viene poi ricostruita in background
reason_cancellation Motivi di rifiuto personalizzati come array (max. 50 voci, 40 caratteri ciascuno; vuoto = elenco standard)

Collaboratori (area collaboratori)

I moduli con la protezione di accesso per collaboratori attivata sono raggiungibili solo dopo il login tramite magic link. I collaboratori e i gruppi alla base li gestisci tramite API:

Metodo Endpoint Descrizione
GET /staff-members Collaboratori inclusa l’assegnazione ai gruppi, source (onoffice/manual) e is_active
POST /staff-members Crea un collaboratore esterno: name, email, facoltativo groups (array di ID di gruppi)
PATCH /staff-members/{id} Aggiornamento parziale: name, email (solo per quelli manuali), groups, is_active
DELETE /staff-members/{id} Elimina un collaboratore (termina anche le sessioni in corso)
POST /staff-members/import-onoffice Importa utenti onOffice: onoffice_user_ids (array, facoltativo – se non specificato, tutti quelli importabili). Risposta: created, updated
GET /staff-groups Gruppi incluso il numero di membri (onoffice_group_id impostato = replicato da onOffice)
POST /staff-groups Crea un gruppo locale (name)
DELETE /staff-groups/{id} Elimina un gruppo
  • Il limite di posti del tuo piano (staff_area.seat_limit in /account) viene applicato rigorosamente alla creazione, all’attivazione e all’importazione (422). Solo i collaboratori attivi occupano un posto.
  • Per i collaboratori onOffice, nome, e-mail e gruppi provengono dalla sincronizzazione giornaliera con onOffice – per questo motivo l’e-mail non è modificabile tramite API.
  • Disattivare (is_active: false) termina immediatamente tutte le sessioni in corso del collaboratore.
  • Se elimini un gruppo utilizzato nelle abilitazioni dei moduli, viene rimosso da esse; se non resta alcuna abilitazione, il modulo non è abilitato per nessuno (volutamente restrittivo) – in tal caso imposta di nuovo l’abilitazione.
  • Quali moduli sono protetti e chi può vederli lo controlli tramite i campi del modulo staff_only_enabled, staff_allowed_member_ids e staff_allowed_group_ids nel PATCH del modulo; gli interruttori a livello di account (staff_area.enabled, session_days) si trovano in /account.

Suggerimenti per Zapier, Make & Co.

  • Trigger «Nuovo invio»: crea un abbonamento al webhook (vedi sopra) – più affidabile e veloce rispetto alle interrogazioni periodiche.
  • Alternativa al polling: prendere nota di GET /forms/{id}/submissions?sort=desc e del valore più alto già elaborato id.
  • Test di connessione: GET /me.
  • Riconoscimento di nuovi moduli: GET /forms?updated_since=….
  • In caso di 429 inserire una breve attesa e rispettare l’intestazione Retry-After.

Gestione delle versioni

La versione attuale è v1 ed è fissata nel percorso (/api/v1). Aggiungiamo nuovi campi ed endpoint in modo retrocompatibile senza modificare la versione; pertanto, il tuo client dovrebbe semplicemente ignorare i campi sconosciuti presenti nelle risposte. Modifiche fondamentali e non retrocompatibili verrebbero pubblicate con una nuova versione (/api/v2).