Ein Formular auf der eigenen Website legt Contacts im CRM an — welcher Endpoint, welcher Key, welche Felder, und was das CRM dabei nicht für Sie tut.
Ein Newsletter-Formular, ein Downloadgatter, ein Kontaktformular: Die Anmeldung soll als Contact in Ihrem Workspace landen. Das geht, und zwar über einen einzigen Aufruf — aber nicht über einen Webhook, und nicht mit Ihrem eigenen API-Key. Diese Seite beschreibt den Weg, den wir empfehlen, und nennt die drei Dinge, die es dafür bewusst nicht gibt.
Webhooks gehen aus dem CRM heraus. Sie abonnieren Events wie contact.created und das CRM ruft Ihren Endpoint. Es gibt keinen Endpoint, auf den ein Formular ohne Authentifizierung posten könnte — ein solcher Endpoint wäre eine offene Schreiboberfläche auf Ihren Datenbestand, und deshalb existiert er nicht.
Ihr Server ruft also das CRM, nicht umgekehrt: POST /api/v1/contacts mit dem Header x-api-key. Das Formular selbst ruft das CRM nie direkt — der Key gehört auf Ihren Server und darf nicht im Browser landen.
Nehmen Sie nicht Ihren persönlichen Key. Ein API-Key erbt die Rechte seines Benutzers, und eigene Rechte pro Key gibt es nicht. Ein Key von Ihrem Account kann alles, was Sie können — auf einem Webserver ist das mehr Vertrauen, als ein Anmeldeformular braucht.
Legen Sie stattdessen unter Unternehmen → Rollen eine eigene Rolle an, laden Sie einen Benutzer dafür ein und erzeugen Sie den Key als dieser Benutzer.
Wie eng die Rolle werden kann, sollten Sie vorher wissen, denn eine Grenze überrascht.
Der Rolleneditor vergibt Anlegen, Bearbeiten und Löschen pro Bereich einzeln, dazu die Leserechte (keine / eigene / alle). Eine Rolle, die nur anlegen darf, ist damit ein einzelnes Häkchen.
Und Leserechte keine funktioniert nicht. Wer nicht alles lesen darf, muss jeden Datensatz, den er anlegt, sich selbst zuweisen — sonst entstünde ein Datensatz, den sein Urheber nicht mehr sehen kann. Das Anlegen scheitert dann mit 400 und „Sie müssen sich selbst zuweisen“ am Feld userIds. Für ein Formular ist das die falsche Richtung: Der Lead soll dem Vertrieb gehören, nicht dem Webserver.
Die engste Rolle, die ein Formular tragen kann, ist damit:
Danach: Profil → API & Konnektoren → Hinzufügen, als dieser Benutzer. Der Key wird einmal angezeigt, gehört in eine Umgebungsvariable Ihres Servers und nicht ins Repository. Details unter API-Keys.
Was das im Zwischenfall wert ist, und was nicht: Ein so beschnittener Key sieht keine Deals, Organisationen, Rechnungen oder Postfächer, und Sie können ihn löschen, ohne dass sonst etwas stehenbleibt. Er liest aber alle Contacts und kann sie auch ändern und löschen, weil der Editor das nicht trennt — Ihr gesamter Kontaktbestand hängt also am Schlüssel auf Ihrem Webserver. Wenn Ihnen das zu viel ist, gehört der Schreibweg hinter einen eigenen kleinen Dienst auf Ihrer Seite, der nur Anlegen durchlässt.
curl -X POST https://crm.virtual-marketer.de/api/v1/contacts \
-H "x-api-key: $VM_CRM_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"firstName": "Jana",
"lastName": "Beck",
"notes": "Media-Kit angefordert am 17.09.2026, Formular /media-kit",
"identifiers": [
{ "provider": "mail", "value": "jana.beck@example.de" }
]
}'Antwort 201 mit dem angelegten Contact, 400 mit einem lesbaren Validierungsfehler.
Was dabei zu wissen ist:
firstName darf nicht leer sein, lastName muss vorhanden sein und darf leer sein. Ein Formular, das nur ein Feld „Name“ hat, schreibt den ganzen Namen in firstName und "" in lastName — das ist gültig und ehrlicher, als einen Nachnamen zu raten.identifiers als { provider, value }, mit provider aus mail, google, outlook, whatsapp. Das ist nicht nur eine andere Schreibweise: Schreibt dieselbe Person später eine Mail oder eine WhatsApp-Nachricht an ein verbundenes Postfach, ordnet der Posteingang sie automatisch genau diesem Contact zu. Eine Adresse, die nur in notes steht, tut das nicht.400, und dann wird gar nichts angelegt. Prüfen Sie das Format auf Ihrer Seite, bevor Sie senden, sonst verlieren Sie die Anmeldung vollständig.customFieldValues als { columnId, value }. Die IDs stehen in der Antwort von GET /api/v1/contacts/configuration; bei einer Auswahl-Spalte ist value die UUID der Option, nicht ihre Beschriftung. Auswahl-Spalten mit einer Standard-Option füllt das CRM beim Anlegen von selbst — ein Lead bekommt also die erste Vertriebsphase, ohne dass Ihr Formular sie mitschickt.Das CRM dedupliziert selbst, aber nur über den Kanal: Eine E-Mail-Adresse oder eine WhatsApp-Nummer kann immer nur an einem Contact hängen. Ein zweiter POST mit einer bereits verknüpften Adresse ist deshalb ein 400:
✖ This channel is already linked to another contact.
→ at identifiers[0].valueDas ist kein Ausfall, sondern die Antwort „kennen wir schon“. Behandeln Sie genau diesen Fall getrennt: nicht wiederholen, nicht als Störung melden — die Person steht bereits im CRM. Jeder andere 400 ist ein echter Fehler in Ihren Daten; ein 5xx oder ein Timeout gehört wiederholt.
Was daraus folgt:
POST /api/v1/contacts/search mit { "searchTerm": "jana.beck@example.de" }.contact.created per Webhook und löst sie dort aus.