Blog

CiviCRM Standalone: Newsletter Double Opt-in Mit Beliebiger Website

Von Johannes Filter ·

Einleitung

Wenn Sie eine Nonprofit- oder Community-Organisation betreiben, möchten Sie wahrscheinlich eine Newsletter-Anmeldung auf Ihrer Website haben. Und wenn Sie CiviCRM als CRM nutzen, sollen neue Abonnent:innen direkt in CiviCRM landen - mit sauberem Double-Opt-In und ohne zusätzlichen Newsletter-Dienst.

CiviCRM Standalone (die neue Deployment-Option, die ohne Drupal, WordPress oder Joomla als Host-CMS läuft) macht das gleichzeitig einfacher und schwieriger. Einfacher, weil das CRM jetzt ein sauber getrennter, eigenständiger Dienst ist. Schwieriger, weil die meisten vorhandenen Anleitungen davon ausgehen, dass CiviCRM in ein CMS eingebettet ist, und viele alte URL-Pfade schlicht nicht funktionieren.

Diese Anleitung zeigt, wie Sie ein Newsletter-Anmeldeformular auf jeder externen Website bauen - egal ob WordPress, statische Hugo-Seite, einfache HTML-Seite oder etwas anderes -, das über die REST API mit CiviCRM Standalone spricht. Das Formular sammelt eine E-Mail-Adresse ein, CiviCRM erstellt oder findet den Kontakt, trägt ihn in Ihre Mailing-Gruppe ein und verschickt automatisch eine Double-Opt-In-Bestätigungsmail. Ich habe mir das meiste davon auf die harte Tour zusammengesucht; hoffentlich spart Ihnen das etwas Zeit.

Vorab: API-Key im Browser ist ein Tradeoff

Die einfachste Variante dieser Anleitung zeigt ein Formular, das die CiviCRM-REST-API direkt aus dem Browser anspricht - der API-Key liegt sichtbar in einem <script>-Tag. Jede:r, der den Quelltext liest, hat ihn. Abschnitt 3 erklärt, wie man den User dahinter so eng zusammenschnürt, dass ein abgegriffener Key keinen großen Schaden anrichten kann - er reduziert sich darauf, einen einzigen FormProcessor auszulösen, sonst nichts. Aber selbst mit Lockdown kann jede:r mit dem Key beliebige Subscribe-Versuche absetzen, was bedeutet: Ihr CiviCRM verschickt Ihre Bestätigungsmails an Adressen, die ein:e Angreifer:in ausgewählt hat. Das ist ein realer Missbrauchsvektor und ein realer Reputationsrisiko für Ihren Mailversand.

Für alles, was über eine kleine Site oder einen schnellen Demo-Build hinausgeht, nehmen Sie lieber den Server-seitigen Proxy aus dem Abschnitt Hardening unten. Damit landet der Key gar nicht erst im Browser, und Sie können Origin, CAPTCHA-Token und Rate-Limits prüfen, bevor an CiviCRM weitergeleitet wird. Der Browser-Direkt-Weg steht hier, weil bestehende Anleitungen ihn voraussetzen und weil der Lockdown aus Abschnitt 3 ihn für risikoarme Setups akzeptabel macht - aber akzeptabel ist nicht ideal.

Voraussetzungen

CiviCRM einrichten

1. Mailing-Gruppe erstellen

Gehen Sie zu Kontakte -> Gruppen verwalten -> Neue Gruppe. Erstellen Sie eine Gruppe (z. B. “Newsletter”) und setzen Sie den Gruppentyp auf “Mailing List”.

Entscheidend: Setzen Sie die Sichtbarkeit auf “Public Pages”. Das übersieht man leicht. Die API-Action MailingEventSubscribe, die den Double-Opt-In-Ablauf auslöst, prüft, ob die Zielgruppe öffentlich ist. Wenn die Sichtbarkeit auf “User and User Admin Only” steht (der Standard), lehnt die API die Anmeldung mit einem “Group is not Public”-Fehler ab. Kein hilfreicher Hinweis, nur eine klare Absage. Also: auf “Public Pages” setzen.

2. FormProcessor konfigurieren

FormProcessor ist eine CiviCRM-Extension, mit der Sie wiederverwendbare API-Actions definieren können - im Grunde benannte Prozeduren, die mehrere Schritte bündeln und als eine einzige API-Aktion verfügbar machen. Wir erstellen eine namens newsletter_signup.

  1. Gehen Sie zu Administer -> Automation -> FormProcessors
  2. Klicken Sie auf “Add Form Processor”
  3. Nennen Sie ihn newsletter_signup (der technische Name ist wichtig - er wird zum API-Action-Namen)
  4. Fügen Sie einen Eingabeparameter hinzu: Name email, Typ “String” (oder “Email”, falls verfügbar), als erforderlich markieren
  5. Fügen Sie eine Action zum Finden oder Erstellen eines Kontakts hinzu:
    • Action-Typ: “Get or Create Contact by Email”
    • Den E-Mail-Eingabeparameter auf diese Action mappen
  6. Fügen Sie eine zweite Action für die Anmeldung hinzu:
    • Action-Typ: “MailingEventSubscribe” (oder eine generische “API Call”-Action mit Entity MailingEventSubscribe und Action create)
    • Den Parameter email auf den E-Mail-Eingabeparameter setzen
    • group_id auf die ID Ihrer Newsletter-Gruppe setzen (die Gruppen-ID finden Sie in der URL beim Bearbeiten der Gruppe oder im API Explorer)
    • contact_id aus dem Ergebnis der vorherigen “Get or Create Contact”-Action mappen

Nach dem Speichern können Sie das im CiviCRM API Explorer testen (Support -> Developer -> API Explorer v4), indem Sie FormProcessor.newsletter_signup mit einer Test-E-Mail-Adresse aufrufen. Wenn alles funktioniert, verschickt CiviCRM eine Bestätigungsmail mit Double-Opt-In-Link an diese Adresse.

3. API-Benutzer minimal absichern

Das Snippet im nächsten Abschnitt schickt Ihren API-Key in den Browser jeder Besucher:in. Jede:r kann ihn aus dem Quelltext kopieren und mit curl wiederverwenden. Behandeln Sie diesen Benutzer also wie bereits kompromittierte Zugangsdaten und vergeben Sie nur das absolute Minimum an Berechtigungen.

Ziel: Selbst wenn der Schlüssel abgegriffen wird, kann er ausschließlich Ihren newsletter_signup-FormProcessor auslösen - nicht Contact.get, nicht Contact.create direkt, keinen anderen API-Aufruf.

Das ist möglich, weil FormProcessor zwei nützliche Eigenschaften hat:

  1. Jeder FormProcessor hat ein konfigurierbares Permission-Feld - die einzige Berechtigung, die zum Auslösen nötig ist (FormProcessor überschreibt die Default-Anforderung administer CiviCRM über den Hook alterAPIPermissions).
  2. Die internen Sub-Actions (“Get or Create Contact by Email”, “MailingEventSubscribe”) laufen mit System-Rechten. Der aufrufende Benutzer braucht kein add contacts oder andere “echte” CiviCRM-Berechtigungen, damit diese Schritte durchlaufen.

Damit funktioniert die Absicherung so:

  1. Eigenen Kontakt und Login-Benutzer anlegen, getrennt vom menschlichen Admin. In Standalone den Login ohne nutzbares Passwort einrichten, sodass das Konto nur per API-Key erreichbar ist.

  2. Eindeutige Permission-Zeichenkette am FormProcessor setzen. Bearbeiten Sie Ihren newsletter_signup-FormProcessor und tragen Sie ins Feld Permission etwas Unverwechselbares ein, z. B. newsletter signup api. Jeder String funktioniert - er muss nirgendwo registriert sein, er muss nur einmalig für genau diesen FormProcessor sein.

  3. Dem API-Benutzer nur zwei Berechtigungen geben (über Roles in Standalone, über das Benutzerprofil in anderen Deployments):

    • access AJAX API - nötig, damit /civicrm/ajax/rest überhaupt antwortet.
    • newsletter signup api - Ihre Custom-Permission aus Schritt 2.
  4. Auf keinen Fall vergeben:

    • administer CiviCRM, access CiviCRM
    • add contacts, view all contacts, edit all contacts
    • access CiviMail subscribe/unsubscribe pages
    • alles rund um Contributions / Memberships / Events
  5. API-Key generieren. Im Kontakt des Benutzers den Reiter API Key öffnen und den Schlüssel erzeugen.

  6. Site-Key. Auf dem Server civicrm.settings.php öffnen und den Wert von CIVICRM_SITE_KEY kopieren:

    define('CIVICRM_SITE_KEY', 'your-site-key-here');
    

Absicherung verifizieren. Mit demselben API-Key einen anderen Endpunkt aufrufen:

curl "https://crm.example.com/civicrm/ajax/rest?entity=Contact&action=get&json=1&api_key=THEKEY&key=THESITEKEY"

Sie sollten einen Permission-Denied-Fehler zurückbekommen. Falls Kontaktdaten erscheinen, hat der Benutzer mehr Rechte als nötig - zurück zu Schritt 4.

Was das trotzdem NICHT verhindert. Auch mit diesem Minimal-Setup kann jede:r mit dem Schlüssel beliebige fremde E-Mail-Adressen an den FormProcessor schicken. Jede Anmeldung löst eine Bestätigungsmail von Ihrer CiviCRM-Instanz aus. Ein:e Angreifer:in kann das nutzen, um Fremde über Ihre Domain zu spammen - schlecht für die Betroffenen und schlecht für die Sende-Reputation Ihres Servers. Permission-Lockdown ist die Untergrenze, nicht das Komplettpaket. Siehe Hardening unten für die Schichten oben drauf.

Das Formular bauen

Hier ist ein vollständiges, eigenständiges HTML/CSS/JS-Snippet, das Sie in jede Website einfügen können. Es spricht die CiviCRM REST API direkt aus dem Browser mit einem einfachen XMLHttpRequest an - keine Abhängigkeiten, kein Build-Schritt, kein jQuery.

Ersetzen Sie die drei Platzhalter-Variablen am Anfang: CIVICRM_URL, API_KEY und SITE_KEY.

<div id="newsletter-signup">
  <style>
    #newsletter-signup {
      max-width: 440px;
      margin: 2rem auto;
      padding: 2rem;
      background: #fff;
      border-radius: 12px;
      box-shadow: 0 2px 16px rgba(0,0,0,0.10);
      font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
    }
    #newsletter-signup h3 {
      margin: 0 0 0.5rem 0;
      font-size: 1.25rem;
    }
    #newsletter-signup p {
      margin: 0 0 1.2rem 0;
      color: #555;
      font-size: 0.95rem;
    }
    #newsletter-signup label {
      display: block;
      font-weight: 600;
      margin-bottom: 0.4rem;
      font-size: 0.95rem;
    }
    #newsletter-signup input[type="email"] {
      width: 100%;
      padding: 0.6rem 0.8rem;
      border: 1px solid #ccc;
      border-radius: 6px;
      font-size: 1rem;
      box-sizing: border-box;
      margin-bottom: 1rem;
    }
    #newsletter-signup input[type="email"]:focus {
      outline: none;
      border-color: #c0392b;
      box-shadow: 0 0 0 2px rgba(192,57,43,0.15);
    }
    #newsletter-signup button {
      width: 100%;
      padding: 0.7rem;
      background: #c0392b;
      color: #fff;
      border: none;
      border-radius: 6px;
      font-size: 1rem;
      font-weight: 600;
      cursor: pointer;
      transition: background 0.2s;
    }
    #newsletter-signup button:hover {
      background: #a93226;
    }
    #newsletter-signup button:disabled {
      background: #ccc;
      cursor: not-allowed;
    }
    #newsletter-signup .nl-msg {
      margin-top: 1rem;
      padding: 0.8rem;
      border-radius: 6px;
      font-size: 0.95rem;
      display: none;
    }
    #newsletter-signup .nl-msg.success {
      background: #eafaf1;
      color: #1e7e34;
      border: 1px solid #b7e4c7;
      display: block;
    }
    #newsletter-signup .nl-msg.error {
      background: #fdecea;
      color: #a94442;
      border: 1px solid #f5c6cb;
      display: block;
    }
  </style>

  <h3>Newsletter abonnieren</h3>
  <p>Erhalten Sie Updates direkt in Ihr Postfach. Wir respektieren Ihre Privatsphäre.</p>

  <form id="nl-form" onsubmit="return nlSubmit(event)">
    <label for="nl-email">E-Mail-Adresse</label>
    <input type="email" id="nl-email"
           placeholder="name@beispiel.de" required>
    <button type="submit" id="nl-btn">Abonnieren</button>
  </form>
  <div id="nl-msg" class="nl-msg"></div>

  <script>
    function nlSubmit(e) {
      e.preventDefault();

      // ===== DIESE DREI WERTE ERSETZEN =====
      var CIVICRM_URL = "https://crm.example.com";
      var API_KEY     = "YOUR_API_KEY";
      var SITE_KEY    = "YOUR_SITE_KEY";
      // ======================================

      var email = document.getElementById("nl-email").value.trim();
      var btn   = document.getElementById("nl-btn");
      var msg   = document.getElementById("nl-msg");

      if (!email) return false;

      btn.disabled = true;
      btn.textContent = "Wird gesendet...";
      msg.className = "nl-msg";
      msg.style.display = "none";

      // CiviCRM Standalone weist GET für schreibende Actions ab ("Destructive HTTP GET"),
      // deshalb POST. Die Keys landen im Body - so tauchen sie nicht im Access-Log
      // des Webservers als URL-Parameter auf.
      var body = "entity=FormProcessor"
        + "&action=newsletter_signup"
        + "&json=1"
        + "&api_key=" + encodeURIComponent(API_KEY)
        + "&key=" + encodeURIComponent(SITE_KEY)
        + "&email=" + encodeURIComponent(email);

      var xhr = new XMLHttpRequest();
      xhr.open("POST", CIVICRM_URL + "/civicrm/ajax/rest", true);
      xhr.setRequestHeader("Content-Type", "application/x-www-form-urlencoded");
      xhr.onreadystatechange = function() {
        if (xhr.readyState !== 4) return;
        btn.disabled = false;
        btn.textContent = "Abonnieren";

        if (xhr.status === 200) {
          try {
            var resp = JSON.parse(xhr.responseText);
            if (resp.is_error && resp.is_error !== 0) {
              msg.textContent = "Fehler: " + (resp.error_message || "Unbekannter Fehler");
              msg.className = "nl-msg error";
            } else {
              msg.textContent = "Vielen Dank! Bitte prüfen Sie Ihr Postfach "
                + "und klicken Sie auf den Bestätigungslink, um "
                + "Ihre Anmeldung abzuschließen (Double Opt-In).";
              msg.className = "nl-msg success";
              document.getElementById("nl-email").value = "";
            }
          } catch(ex) {
            msg.textContent = "Unerwartete Antwort vom Server.";
            msg.className = "nl-msg error";
          }
        } else {
          msg.textContent = "Anfrage fehlgeschlagen (HTTP " + xhr.status
            + "). Bitte versuchen Sie es später erneut.";
          msg.className = "nl-msg error";
        }
      };
      xhr.send(body);
      return false;
    }
  </script>
</div>

Dieses Snippet ist vollständig eigenständig. Die Styles sind auf den Wrapper #newsletter-signup beschränkt, damit sie nicht mit den vorhandenen Styles Ihrer Website kollidieren. Es rendert eine schlichte Karte mit Schatten, abgerundeten Ecken und rotem Absende-Button.

CORS konfigurieren

Wenn Ihre Website (z. B. www.example.com) und CiviCRM (z. B. crm.example.com) auf unterschiedlichen Domains liegen, blockiert der Browser den AJAX-Request, sofern der CiviCRM-Server nicht die passenden CORS-Header sendet.

Für Apache fügen Sie Folgendes in die .htaccess im Webroot Ihrer CiviCRM-Standalone-Installation ein:

<IfModule mod_headers.c>
  Header set Access-Control-Allow-Origin "https://www.example.com"
  Header set Access-Control-Allow-Methods "GET, POST, OPTIONS"
  Header set Access-Control-Allow-Headers "Content-Type"
</IfModule>

Wenn Sie mehrere Origins erlauben müssen, brauchen Sie eine dynamischere Konfiguration mit SetEnvIf oder einer Rewrite-Regel. Für eine schnelle Entwicklungsumgebung können Sie * als Origin verwenden; in Produktion sollten Sie immer auf Ihre tatsächliche Domain einschränken.

Für nginx verwenden Sie add_header-Direktiven im Server- oder Location-Block:

location / {
    add_header Access-Control-Allow-Origin "https://www.example.com" always;
    add_header Access-Control-Allow-Methods "GET, POST, OPTIONS" always;
    add_header Access-Control-Allow-Headers "Content-Type" always;

    # Preflight-Requests beantworten
    if ($request_method = OPTIONS) {
        return 204;
    }
}

Starten Sie nach dem Hinzufügen der CORS-Header den Webserver neu und testen Sie erneut. Öffnen Sie die Entwicklerkonsole des Browsers und prüfen Sie, dass der Request an crm.example.com keinen CORS-Fehler mehr zeigt.

WordPress-Integration

Wenn Ihre öffentliche Website auf WordPress läuft, haben Sie je nach Theme zwei Optionen: klassisches Theme oder Block-Theme.

Option A: Contact Form 7 + CiviCRM Integration Plugin (klassische Themes)

Dieser Ansatz funktioniert gut mit klassischen Themes und gibt Ihnen ein Formular, das Sie im WordPress-Admin verwalten können.

  1. Installieren und aktivieren Sie das Plugin Contact Form 7
  2. Installieren und aktivieren Sie das Plugin “Contact Form 7 CiviCRM Integration”
  3. Erstellen Sie ein neues Contact-Form-7-Formular mit einem E-Mail-Feld
  4. Konfigurieren Sie im CiviCRM-Integration-Tab des Formulars:
    • CiviCRM Host: https://crm.example.com
    • API Key: Ihr API-Key
    • Site Key: Ihr Site-Key
    • REST Path: /civicrm/ajax/rest
  5. Setzen Sie die Action auf FormProcessor.newsletter_signup und mappen Sie das E-Mail-Feld

Wichtig: Achten Sie darauf, dass der REST-Pfad auf /civicrm/ajax/rest gesetzt ist und nicht auf den alten Pfad /sites/all/modules/civicrm/extern/rest.php oder /core/extern/rest.php. CiviCRM Standalone hat keine civicrm.config.php an der Stelle, an der extern/rest.php sie erwartet; dieser Endpunkt liefert deshalb einen HTTP-500-Fehler. Die Route /civicrm/ajax/rest läuft durch das normale CiviCRM-Routing und funktioniert korrekt mit Standalone.

Option B: HTML-Block im Block-Editor (Block-Themes)

Wenn Sie ein modernes Block-Theme verwenden (oder einfach keine zusätzlichen Plugins installieren möchten), können Sie das HTML/JS-Snippet direkt in einen Custom HTML-Block im WordPress-Block-Editor einfügen.

Das funktioniert - mit einer wichtigen Einschränkung.

WordPress entfernt <script>, <form> und <input>-Tags. WordPress hat einen Sicherheitsfilter namens kses, der HTML-Inhalte bereinigt. Wenn Sie eine Seite über den Block-Editor speichern (der intern die REST API nutzt), läuft kses und entfernt still alle Tags, die als unsicher gelten - darunter <script>, <form> und oft auch <input>. Sie fügen das Snippet ein, klicken auf Speichern, laden die Seite neu und stellen fest: Formular und JavaScript sind einfach verschwunden.

Die Lösung ist, kses-Filtering für REST-API-Requests zu deaktivieren. Legen Sie dafür ein kleines Must-Use-Plugin (mu-plugin) unter wp-content/mu-plugins/disable-kses-rest.php an:

<?php
/**
 * Plugin Name: Disable kses for REST API
 * Description: Verhindert, dass WordPress script/form/input-Tags
 *              beim Speichern via REST API entfernt (Block-Editor).
 */

add_action('rest_api_init', function() {
    if (current_user_can('unfiltered_html')) {
        kses_remove_filters();
    }
});

Das prüft, ob der aktuelle Benutzer die Berechtigung unfiltered_html hat (Administratoren haben sie standardmäßig), bevor der Filter deaktiviert wird. So können nur vertrauenswürdige Benutzer ungefiltertes HTML speichern, während die Einschränkung für niedrigere Rollen bestehen bleibt.

Gehen Sie nach dem Anlegen der Datei zurück zur Seite, fügen Sie das HTML/JS-Snippet in einen Custom-HTML-Block ein und speichern Sie. Diesmal bleiben Formular- und Script-Tags erhalten.

Integration in andere CMS / statische Websites

Das HTML/JS-Snippet von oben ist einfacher, dependency-freier Code. Es funktioniert überall, wo Sie HTML und JavaScript platzieren können. Ein paar konkrete Hinweise:

Sie brauchen nur eine Seite, die über HTTPS ausgeliefert wird (für sichere API-Aufrufe), und korrekt konfigurierte CORS-Header auf dem CiviCRM-Server, falls die Domains unterschiedlich sind.

Hardening

Der eingeschränkte API-Benutzer aus Abschnitt 3 ist das Minimum, das Sie ausliefern sollten. Für sich allein lässt er aber weiterhin zu, dass jemand mit dem Key Subscribe-Versuche an beliebige Adressen absetzt, was heißt: Ihr CiviCRM verschickt Ihre Bestätigungsmails an Personen, die ein:e Angreifer:in ausgesucht hat. Die drei folgenden Maßnahmen schließen die verbleibende Lücke. Die dritte - der Server-seitige Proxy - ist die, die ich für jede produktive Site tatsächlich empfehlen würde; die ersten beiden sind auch dann nützlich, wenn Sie schon einen Proxy haben.

Server-seitiger Proxy (für alles Ernste empfohlen)

Das richtige Muster für Production ist, den API-Key gar nicht erst in den Browser zu legen. Stattdessen:

  1. Das Formular auf der öffentlichen Website postet an einen eigenen kleinen Endpunkt (eine WordPress-REST-Route, einen Cloudflare Worker, eine Netlify Function, einen Mini-Server).
  2. Dieser Endpunkt hält den API-Key serverseitig in einer Umgebungsvariable.
  3. Der Endpunkt prüft das CAPTCHA-Token, optional die Origin, setzt sein eigenes Rate-Limit, und ruft erst dann CiviCRM auf.

Damit ist das Leak-Problem vom Tisch. Der Preis: ein Stück Backend-Code zu pflegen - aber das ist wirklich klein (~50 Zeilen PHP/JS). Für WordPress-Seiten erledigt das bestehende Plugin CF7-CiviCRM-Integration genau das: Sie binden ein Contact-Form-7-Formular ein, das Plugin hält den API-Key serverseitig in den WP-Options, und CiviCRM wird vom WP-Backend aus aufgerufen. Das ist ein Abend Setup-Aufwand und liefert Ihnen einen Server-seitigen Proxy ohne eigenen Code.

CAPTCHA einbauen

Setzen Sie Cloudflare Turnstile (kostenlos, ohne Bilderrätsel) oder hCaptcha vor das Formular. Das Widget produziert ein Token, das Sie serverseitig prüfen, bevor Sie an CiviCRM weiterleiten. Das setzt den Server-seitigen Proxy aus dem Abschnitt davor voraus - es gibt keinen sauberen Weg, ein CAPTCHA-Token aus einem reinen Browser-Setup zu verifizieren.

/civicrm/ajax/rest rate-limitieren

Wenn CiviCRM hinter Cloudflare läuft, eine Rate-Limiting-Regel einrichten (z. B. 5 Requests pro Minute pro IP für /civicrm/ajax/rest). Bei nginx geht das Gleiche mit limit_req_zone und limit_req. Wählen Sie eine niedrige Zahl - reguläre Nutzer:innen senden das Formular nur einmal ab. Das hilft unabhängig davon, ob Sie den Browser-Direkt-Weg oder einen Proxy nutzen.

Troubleshooting / Häufige Fallstricke

Hier sind die Probleme, über die ich gestolpert bin, damit Sie direkt zur Lösung springen können.

extern/rest.php gibt HTTP 500 zurück

Wenn Sie https://crm.example.com/core/extern/rest.php (oder irgendeinen extern/rest.php-Pfad) aufrufen und einen 500er bekommen, liegt das daran, dass CiviCRM Standalone die Datei civicrm.config.php nicht an der Stelle ablegt, an der extern/rest.php sie laden möchte. Die Lösung ist einfach: Verwenden Sie stattdessen /civicrm/ajax/rest als API-Endpunkt. Das ist die moderne, unterstützte Route für die REST API von CiviCRM Standalone.

“Group is not Public”-Fehler

Wenn Sie beim Aufruf von MailingEventSubscribe.create diesen Fehler sehen, ist die Sichtbarkeit Ihrer Ziel-Mailing-Gruppe auf “User and User Admin Only” gesetzt. Gehen Sie zu Kontakte -> Gruppen verwalten, bearbeiten Sie die Gruppe und ändern Sie die Sichtbarkeit auf “Public Pages”. Die MailingEventSubscribe-API erzwingt diese Prüfung, weil der Double-Opt-In-Ablauf für öffentliche Anmeldungen gedacht ist.

“SECURITY: Destructive HTTP GET”-Fehler

Wenn der Request {"error_message":"SECURITY: All requests that modify the database must be http POST, not GET.","is_error":1} zurückgibt, schicken Sie einen GET-Request an eine schreibende Action. CiviCRM Standalone erzwingt POST für alle API-Aufrufe, die Daten ändern - inklusive MailingEventSubscribe.create und des FormProcessors, der ihn umschließt. Die Lösung: XMLHttpRequest (oder fetch) auf POST umstellen und die Parameter in den Request-Body packen, nicht in die URL. Das Snippet im Abschnitt Das Formular bauen macht das bereits so; ältere Versionen dieser Anleitung verwendeten GET und liefen genau in diesen Fehler.

CORS-Fehler in der Browser-Konsole

Wenn Sie Fehler wie Access to XMLHttpRequest at 'https://crm.example.com/...' from origin 'https://www.example.com' has been blocked by CORS policy sehen, sendet Ihr CiviCRM-Server nicht die richtigen CORS-Header. Siehe Abschnitt CORS-Konfiguration oben. Denken Sie daran, Apache oder nginx nach dem Hinzufügen der Header neu zu starten. Beachten Sie außerdem, dass Browser CORS-Preflight-Antworten recht aggressiv cachen können - machen Sie einen Hard Refresh oder testen Sie in einem privaten Fenster.

WordPress entfernt Formular-/Script-Tags

Sie fügen das HTML-Snippet in einen Custom-HTML-Block ein, speichern, und die <script>- und <form>-Tags verschwinden. Das ist die kses-Sanitization von WordPress. Der Block-Editor speichert Inhalte über die REST API, und kses läuft bei REST-API-Requests. Installieren Sie das mu-plugin aus dem WordPress-Abschnitt oben, um kses für Admin-Benutzer bei REST-API-Requests zu deaktivieren.

API-Berechtigungsfehler bei System.get

Einige CiviCRM-API-Integrationen (z. B. das CF7-CiviCRM-Plugin) rufen System.get auf, um die Verbindung zu validieren. Diese Action benötigt administer CiviCRM. Der eingeschränkte API-Benutzer aus Abschnitt 3 hat diese Berechtigung nicht - und soll sie auch nicht haben. Der Verbindungstest schlägt also fehl; die eigentliche Newsletter-Anmeldung funktioniert trotzdem. Behandeln Sie das fehlgeschlagene Test-Result als erwartet und gehen Sie weiter. Wenn Sie unbedingt einen grünen Verbindungstest brauchen, machen Sie ihn einmalig mit einem temporären Admin-Key und tauschen den Key danach gegen den eingeschränkten für den Produktionsbetrieb aus.


Das war’s. Wenn alles verbunden ist, läuft der Ablauf so: Besucher:in trägt E-Mail ein -> Ihr Formular ruft die REST API von CiviCRM auf -> FormProcessor erstellt/findet den Kontakt und meldet ihn an -> CiviCRM verschickt eine Double-Opt-In-Bestätigungsmail -> Besucher:in klickt den Link -> die Anmeldung ist abgeschlossen. Kein zusätzlicher Newsletter-Dienst, volle Kontrolle über Ihre Daten, und es funktioniert mit jedem Frontend.