<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Rahul Sharma</title>
    <description>The latest articles on DEV Community by Rahul Sharma (@rahul_sharma_15bd129bc69e).</description>
    <link>https://dev.to/rahul_sharma_15bd129bc69e</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3476785%2F7115035a-75f6-4d9a-926e-0da2af1b4b40.png</url>
      <title>DEV Community: Rahul Sharma</title>
      <link>https://dev.to/rahul_sharma_15bd129bc69e</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/rahul_sharma_15bd129bc69e"/>
    <language>en</language>
    <item>
      <title>CF7 Form Submits Successfully But Nothing Appears in Airtable Here Is Why</title>
      <dc:creator>Rahul Sharma</dc:creator>
      <pubDate>Fri, 10 Jul 2026 10:25:03 +0000</pubDate>
      <link>https://dev.to/rahul_sharma_15bd129bc69e/cf7-form-submits-successfully-but-nothing-appears-in-airtable-here-is-why-1e4k</link>
      <guid>https://dev.to/rahul_sharma_15bd129bc69e/cf7-form-submits-successfully-but-nothing-appears-in-airtable-here-is-why-1e4k</guid>
      <description>&lt;p&gt;You set up a CF7 form, connected it to Airtable, and submitted a test entry. The form showed the success message. But when you opened your Airtable base, the record was nowhere to be found.&lt;/p&gt;

&lt;p&gt;No error. No failed notification. Just silence.&lt;/p&gt;

&lt;p&gt;This was posted on the WordPress support forums and it is one of the most common Airtable integration complaints. The form appears to work perfectly. Airtable receives nothing. Here are the reasons this happens and exactly how to fix each one.&lt;/p&gt;

&lt;h2&gt;
  
  
  Reason 1: The API Token Has Insufficient Scopes
&lt;/h2&gt;

&lt;p&gt;Airtable moved from legacy API keys to Personal Access Tokens (PATs) in 2024. A PAT only has the permissions you explicitly grant it when you create it.&lt;/p&gt;

&lt;p&gt;To write records to an Airtable base from an external integration, the token needs the &lt;code&gt;data.records:write&lt;/code&gt; scope. Without it, the API call authenticates successfully but Airtable rejects the write operation. Your form submits. The plugin fires. The API call returns a 403 error that the plugin either logs silently or ignores entirely.&lt;/p&gt;

&lt;p&gt;Go to &lt;code&gt;airtable.com/create/tokens&lt;/code&gt;, open the token you are using, and confirm &lt;code&gt;data.records:write&lt;/code&gt; is listed in the scopes. If it is not there, add it and save. Then test again.&lt;/p&gt;

&lt;h2&gt;
  
  
  Reason 2: The Base ID or Table Name Is Wrong
&lt;/h2&gt;

&lt;p&gt;Airtable's API uses the base ID and table name to route your records to the correct location. Both have to be exactly right.&lt;/p&gt;

&lt;p&gt;The base ID looks like &lt;code&gt;appXXXXXXXXXXXXXX&lt;/code&gt;. It appears in the URL when you open the base in your browser. Do not use the base name. Use the ID.&lt;/p&gt;

&lt;p&gt;The table name must match exactly what appears in Airtable, including capitalisation and spacing. If your table is called &lt;code&gt;Contact Form Leads&lt;/code&gt; in Airtable, your integration must send that exact string. &lt;code&gt;contact form leads&lt;/code&gt; or &lt;code&gt;ContactFormLeads&lt;/code&gt; will not match and Airtable will return a 404 or route to nothing.&lt;/p&gt;

&lt;h2&gt;
  
  
  Reason 3: The Field Names in the Payload Do Not Match Airtable
&lt;/h2&gt;

&lt;p&gt;Every record sent to Airtable contains field names and values. The field names in your API payload must exactly match the field names in your Airtable table.&lt;/p&gt;

&lt;p&gt;If your Airtable table has a field called &lt;code&gt;Full Name&lt;/code&gt; and your integration sends &lt;code&gt;name&lt;/code&gt; or &lt;code&gt;fullname&lt;/code&gt;, Airtable either creates an empty record (if required fields are missing) or ignores the value entirely.&lt;/p&gt;

&lt;p&gt;The safest way to confirm your field names is to open your Airtable table, click on any field header, and note the exact name. Then check your integration's field mapping and make sure every name matches character for character.&lt;/p&gt;

&lt;h2&gt;
  
  
  Reason 4: The Plugin Is Catching a Silent Error
&lt;/h2&gt;

&lt;p&gt;Some CF7 to Airtable plugins do not surface errors visibly when the API call fails. They fire the request, get a 401 or 403 back from Airtable, log nothing or log to a file nobody checks, and the form appears to succeed from the user's perspective.&lt;/p&gt;

&lt;p&gt;Enable WordPress debug logging and submit a test form. Check &lt;code&gt;wp-content/debug.log&lt;/code&gt; immediately afterward for any error from the Airtable API call. A 401 means authentication failed. A 403 means authenticated but not authorised. A 422 means the payload was received but failed validation.&lt;/p&gt;

&lt;h2&gt;
  
  
  Reason 5: CF7 Is Flagging the Submission as Spam
&lt;/h2&gt;

&lt;p&gt;CF7 has a spam detection layer that runs before any integration fires. If the submission is caught by Akismet, CF7's honeypot, or a CAPTCHA that fired incorrectly, the form shows a success message but CF7 marks the submission internally as spam and does not fire the hooks that trigger Airtable integrations.&lt;/p&gt;

&lt;p&gt;Submit the form with clearly non-spam data during testing. Temporarily disable Akismet if you have it active. If Airtable starts receiving records after disabling spam filtering, the submission was being flagged.&lt;/p&gt;

&lt;h2&gt;
  
  
  A More Reliable Way to Connect CF7 to Airtable
&lt;/h2&gt;

&lt;p&gt;Dedicated CF7 to Airtable plugins handle the connection for you but give you limited visibility when things go wrong. When the record does not appear and there is no visible error, debugging is guesswork.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.contactformtoapi.com/contact-form-7-third-party-integration-guide/" rel="noopener noreferrer"&gt;Contact Form to API&lt;/a&gt; connects CF7 directly to Airtable's REST API. You configure the Airtable endpoint URL, add your PAT as a Bearer Authorization header, and map your CF7 form fields to the exact Airtable field names your table uses. Every submission attempt logs the response from Airtable, so you see whether the record was created, and if not, you see the exact error Airtable returned. No guesswork about whether the token scope is right or whether the field name matched.&lt;/p&gt;

&lt;h2&gt;
  
  
  Checklist Before Debugging Further
&lt;/h2&gt;

&lt;p&gt;Before spending more time on this, go through these quickly.&lt;/p&gt;

&lt;p&gt;Confirm your PAT has &lt;code&gt;data.records:write&lt;/code&gt; in its scopes.&lt;/p&gt;

&lt;p&gt;Confirm the base ID in your integration starts with &lt;code&gt;app&lt;/code&gt; and matches the URL of your Airtable base.&lt;/p&gt;

&lt;p&gt;Confirm the table name in your integration matches exactly what appears in Airtable including capitalisation.&lt;/p&gt;

&lt;p&gt;Confirm field names in the payload match field names in the table exactly.&lt;/p&gt;

&lt;p&gt;Enable WP_DEBUG_LOG and check the log after a test submission for any API error response.&lt;/p&gt;

&lt;p&gt;Temporarily disable Akismet and retest to rule out spam flagging.&lt;/p&gt;

</description>
      <category>wordpress</category>
      <category>airtable</category>
      <category>api</category>
      <category>webdev</category>
    </item>
    <item>
      <title>CF7 Salesforce Integration Breaks After a WordPress Update - Here Is Why and How to Fix It</title>
      <dc:creator>Rahul Sharma</dc:creator>
      <pubDate>Wed, 08 Jul 2026 11:25:35 +0000</pubDate>
      <link>https://dev.to/rahul_sharma_15bd129bc69e/cf7-salesforce-integration-breaks-after-a-wordpress-update-here-is-why-and-how-to-fix-it-1md0</link>
      <guid>https://dev.to/rahul_sharma_15bd129bc69e/cf7-salesforce-integration-breaks-after-a-wordpress-update-here-is-why-and-how-to-fix-it-1md0</guid>
      <description>&lt;p&gt;A developer updated their client's WordPress site to 5.3.2 and updated the CF7 Salesforce plugin at the same time. Both updates completed without issue. But from that point on, no new records were being created in Salesforce. The Salesforce log showed this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Data Sent: Nothing posted to Salesforce
Message: POST requires content-length
errorCode: UNKNOWN_EXCEPTION
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The form itself was working fine. Flamingo showed every submission. Salesforce received nothing.&lt;/p&gt;

&lt;p&gt;The plugin author said to check field mapping. The developer adjusted the mapping and it worked again. But neither the forum post nor the plugin author explained why a WordPress update would break a field mapping that was working perfectly before.&lt;/p&gt;

&lt;p&gt;This post explains exactly what happened, what the error message actually means, and how to build a CF7 to Salesforce integration that does not break when WordPress updates.&lt;/p&gt;

&lt;h2&gt;
  
  
  What "POST requires content-length" Actually Means
&lt;/h2&gt;

&lt;p&gt;The error &lt;code&gt;POST requires content-length&lt;/code&gt; is a Salesforce API error, not a WordPress error. It means the HTTP request that arrived at Salesforce's endpoint had an empty or missing body. Salesforce expected a JSON payload with lead data and received nothing.&lt;/p&gt;

&lt;p&gt;This does not happen because WordPress updated. WordPress updates do not change how form data is collected or how field mapping works. What actually happened is that the plugin update that ran at the same time reset or altered the field mapping configuration.&lt;/p&gt;

&lt;p&gt;When a plugin updates, it can reset custom settings if the update involves schema changes to how settings are stored. The developer's field mapping was wiped or changed by the plugin update, not by the WordPress update. The form submitted successfully, the plugin tried to POST to Salesforce, sent an empty body because no fields were mapped, and Salesforce returned the content-length error.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why Field Mapping Breaks Silently
&lt;/h2&gt;

&lt;p&gt;The CF7 Salesforce plugin passes the form's submitted data to Salesforce based on the mapping you configure. Each CF7 field tag gets mapped to a Salesforce field name like &lt;code&gt;FirstName&lt;/code&gt;, &lt;code&gt;LastName&lt;/code&gt;, &lt;code&gt;Email&lt;/code&gt;, &lt;code&gt;Company&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;When the mapping is correct, the plugin builds a JSON body like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"FirstName"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Jane"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"LastName"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Smith"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"Email"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"jane@example.com"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"Company"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Acme Corp"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When the mapping is empty or broken, the plugin sends this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Salesforce receives an empty object. Its validation requires &lt;code&gt;LastName&lt;/code&gt; at minimum for a Lead record. No &lt;code&gt;LastName&lt;/code&gt; and no content in the body means the request fails with the content-length error. The plugin logs "Nothing posted to Salesforce" because the payload was genuinely empty.&lt;/p&gt;

&lt;p&gt;The issue is that this fails silently from the user's perspective. The form shows success. Flamingo logs the submission. Nothing in the UI indicates that the Salesforce write produced an empty payload.&lt;/p&gt;

&lt;h2&gt;
  
  
  How to Verify Your Field Mapping After Any Plugin Update
&lt;/h2&gt;

&lt;p&gt;After updating any CF7 CRM integration plugin, always open the plugin's field mapping settings and confirm your mappings are still in place before assuming the integration is working.&lt;/p&gt;

&lt;p&gt;For the CF7 Salesforce plugin specifically, go to the form settings and open the Salesforce Feeds or Mapping section. Confirm every required Salesforce field (&lt;code&gt;FirstName&lt;/code&gt;, &lt;code&gt;LastName&lt;/code&gt;, &lt;code&gt;Email&lt;/code&gt;, &lt;code&gt;Company&lt;/code&gt;) has a CF7 form field mapped to it. Submit a test form and check the Salesforce log immediately for the status.&lt;/p&gt;

&lt;p&gt;If the log shows a Salesforce ID being returned, the record was created. If it shows &lt;code&gt;N/A&lt;/code&gt; with the content-length error, your mapping is empty.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Longer-Term Problem: Integration Fragility
&lt;/h2&gt;

&lt;p&gt;The real issue here is not a bug. It is fragility. A plugin update wiped a mapping configuration that was working correctly. The integration broke silently. Leads were lost until someone noticed.&lt;/p&gt;

&lt;p&gt;This is a structural problem with dedicated CF7 CRM plugins that store your integration configuration in their own database schema. When they update that schema, existing configurations can be reset, altered, or orphaned.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.contactformtoapi.com/how-to-send-contact-form-7-leads-to-salesforce-using-contact-form-to-any-api-pro/" rel="noopener noreferrer"&gt;Contact Form to API&lt;/a&gt; takes a different approach. Your integration is configured as a direct API request with an explicit JSON payload body. The field mapping lives in a request body you define, like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"FirstName"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"[your-name]"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"LastName"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"[last-name]"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"Email"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"[your-email]"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"Company"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"[your-subject]"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This mapping does not live in a proprietary database schema that can be reset by a plugin update. It is part of your integration configuration that you define and control. When you update the plugin, your payload definition stays intact. A 201 response in the API logs confirms every record was created successfully in Salesforce.&lt;/p&gt;

&lt;p&gt;The Pro version with the OAuth Add-on handles Salesforce's OAuth 2.0 authentication flow directly from WordPress, connecting to Salesforce's Lead API at &lt;code&gt;POST /services/data/v63.0/sobjects/Lead/&lt;/code&gt; without any Zapier or middleware dependency.&lt;/p&gt;

&lt;h2&gt;
  
  
  Salesforce Required Fields That Must Always Be Mapped
&lt;/h2&gt;

&lt;p&gt;Salesforce requires at minimum one field for a Lead record to be created. In practice you need at least &lt;code&gt;LastName&lt;/code&gt; and &lt;code&gt;Company&lt;/code&gt;. Without these, Salesforce returns a validation error regardless of whether the other fields are correct.&lt;/p&gt;

&lt;p&gt;Make sure these are always in your mapping:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;CF7 Field&lt;/th&gt;
&lt;th&gt;Salesforce API Field&lt;/th&gt;
&lt;th&gt;Required&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Name field&lt;/td&gt;
&lt;td&gt;&lt;code&gt;LastName&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Company field&lt;/td&gt;
&lt;td&gt;&lt;code&gt;Company&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Email field&lt;/td&gt;
&lt;td&gt;&lt;code&gt;Email&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Recommended&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Phone field&lt;/td&gt;
&lt;td&gt;&lt;code&gt;Phone&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Optional&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;If your CF7 form does not have separate first name and last name fields, map your full name field to &lt;code&gt;LastName&lt;/code&gt;. Salesforce will accept it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Checklist After Any WordPress or Plugin Update
&lt;/h2&gt;

&lt;p&gt;Before trusting that your Salesforce integration survived an update, go through this list.&lt;/p&gt;

&lt;p&gt;Open the field mapping settings in your CF7 Salesforce plugin and confirm all mappings are present.&lt;/p&gt;

&lt;p&gt;Submit a test form with real-looking data, not just "test &lt;a href="mailto:test@test.com"&gt;test@test.com&lt;/a&gt;."&lt;/p&gt;

&lt;p&gt;Check the Salesforce log immediately and confirm a Salesforce ID was returned.&lt;/p&gt;

&lt;p&gt;Log into Salesforce and search for the test contact you just submitted to confirm it actually appears as a Lead record.&lt;/p&gt;

&lt;p&gt;If any step fails, check the mapping first before investigating anything else.&lt;/p&gt;

</description>
      <category>wordpress</category>
      <category>salesforce</category>
      <category>crm</category>
      <category>webdev</category>
    </item>
    <item>
      <title>Random CF7 Submissions Not Reaching Google Sheets: The Causes Nobody Talks About</title>
      <dc:creator>Rahul Sharma</dc:creator>
      <pubDate>Tue, 07 Jul 2026 10:13:27 +0000</pubDate>
      <link>https://dev.to/rahul_sharma_15bd129bc69e/random-cf7-submissions-not-reaching-google-sheets-the-causes-nobody-talks-about-227d</link>
      <guid>https://dev.to/rahul_sharma_15bd129bc69e/random-cf7-submissions-not-reaching-google-sheets-the-causes-nobody-talks-about-227d</guid>
      <description>&lt;p&gt;A site owner posted on the WordPress forums with one of the most frustrating problems in CF7 integrations. Out of every 10 form submissions, 1 or 2 would silently disappear. Flamingo confirmed the form submitted successfully. No error appeared in debug.log. No error showed in the browser. The lead was just gone. Submitting the same form again moments later would work perfectly.&lt;/p&gt;

&lt;p&gt;The plugin author suggested reauthenticating and promised an update. The thread was marked resolved. The problem was not actually resolved.&lt;/p&gt;

&lt;p&gt;Random, intermittent failures with no error log are harder to diagnose than consistent failures. When something breaks every time, you can trace it. When it breaks 15% of the time for no apparent reason, the cause is almost always something outside the plugin itself.&lt;/p&gt;

&lt;p&gt;Here are the four real reasons this happens.&lt;/p&gt;

&lt;h2&gt;
  
  
  Cause 1: Google Sheets API Rate Limiting
&lt;/h2&gt;

&lt;p&gt;Google's Sheets API has rate limits. The free tier allows 100 read and write requests per 100 seconds per user. If your form receives bursts of traffic where multiple people submit within the same short window, you can hit this limit. When the limit is exceeded, Google returns a 429 error and the write is dropped.&lt;/p&gt;

&lt;p&gt;The plugin in this thread had no retry logic at the time of the forum post. When Google returned a 429, the plugin received it, had nowhere to put it, logged nothing, and moved on. The submission sat in Flamingo but never reached Sheets.&lt;/p&gt;

&lt;p&gt;You can verify this by checking your Google Cloud Console under APIs and Services, then Google Sheets API, then Quotas. If you see a spike in quota usage that correlates with your missing submissions, rate limiting is the cause.&lt;/p&gt;

&lt;p&gt;The fix is either to implement exponential backoff retry logic (attempt the write again after 1 second, then 2, then 4) or to upgrade to a higher Google API quota if your volume justifies it. You can also switch to a service account instead of OAuth, which has separate quota tracking and often higher effective limits.&lt;/p&gt;

&lt;h2&gt;
  
  
  Cause 2: PHP Execution Timeout
&lt;/h2&gt;

&lt;p&gt;The system info in the forum post showed &lt;code&gt;Memory Limit: 40M&lt;/code&gt; and &lt;code&gt;Time Limit: 90&lt;/code&gt; seconds. The memory limit is low for a site running WP Rocket, multiple page builders, and several active plugins. The time limit is high, but it matters less than memory.&lt;/p&gt;

&lt;p&gt;When a CF7 form submits, the plugin makes an outbound API call to Google. If the server is under load at that moment, this API call can be slow. If the PHP process runs out of memory before the API call completes, the process is killed mid-execution. The Google Sheets write never happens. The form submission itself had already been processed by CF7 (which is why Flamingo captured it), but the Google Sheets call that runs after it never finished.&lt;/p&gt;

&lt;p&gt;This is random by nature because server load varies. Most submissions happen when the server has capacity. Occasional submissions happen during traffic spikes or resource-heavy background tasks, and those are the ones that fail.&lt;/p&gt;

&lt;p&gt;Check your PHP error log for &lt;code&gt;Allowed memory size exhausted&lt;/code&gt; errors. If you see them, increasing &lt;code&gt;memory_limit&lt;/code&gt; in &lt;code&gt;php.ini&lt;/code&gt; or &lt;code&gt;.htaccess&lt;/code&gt; is the fix.&lt;/p&gt;

&lt;h2&gt;
  
  
  Cause 3: WP Rocket Caching the Form Submission
&lt;/h2&gt;

&lt;p&gt;The system info in the forum post showed WP Rocket active. WP Rocket is a powerful caching plugin but it is known to interfere with certain AJAX-based form submissions if its settings are not configured correctly.&lt;/p&gt;

&lt;p&gt;CF7 form submissions use AJAX. WP Rocket can cache AJAX responses, delay JavaScript execution, or defer scripts in a way that changes when CF7's submission handler fires. If the AJAX request that sends data to Google Sheets gets intercepted or delayed by WP Rocket's cache or script optimisation, the request can fire at the wrong time or not at all.&lt;/p&gt;

&lt;p&gt;Check WP Rocket's settings under File Optimization and confirm that CF7's JavaScript files are excluded from deferral. The relevant file is &lt;code&gt;contact-form-7/includes/js/index.js&lt;/code&gt;. Also check that the CF7 AJAX action (&lt;code&gt;wpcf7_submit&lt;/code&gt;) is excluded from WP Rocket's AJAX caching.&lt;/p&gt;

&lt;p&gt;The fact that the problem is intermittent and that resubmitting immediately works is consistent with a caching layer interfering. The first submission hits a cached response. The second submission hits the live server.&lt;/p&gt;

&lt;h2&gt;
  
  
  Cause 4: Google OAuth Token Expiry Mid-Session
&lt;/h2&gt;

&lt;p&gt;As covered in another post about the &lt;code&gt;invalid_grant&lt;/code&gt; error, Google OAuth tokens expire. If the token expires while your site is handling a submission, the API call to Google Sheets will fail silently if the plugin does not surface the error properly.&lt;/p&gt;

&lt;p&gt;The intermittent nature fits this cause too. Tokens expire at a specific time. Submissions that arrive just after expiry fail. Submissions that arrive after the token has been refreshed succeed.&lt;/p&gt;

&lt;p&gt;Check the plugin's settings for when OAuth was last authorised. If the Google Cloud Console OAuth app is in Testing status, tokens expire after 7 days. Set it to Production to get long-lived tokens.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Reliable Alternative: Direct API Integration
&lt;/h2&gt;

&lt;p&gt;All four causes above have one thing in common. They are amplified by having a middleware plugin manage the Google connection on your behalf. When the plugin mishandles a rate limit, fails to log an error, or loses a write during a resource spike, you have no visibility and no way to retry.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.contactformtoapi.com/automate-wordpress-form-submissions/" rel="noopener noreferrer"&gt;Contact Form to API&lt;/a&gt; calls the Google Sheets API directly using a service account Bearer token instead of OAuth. Service account tokens do not expire the same way OAuth tokens do, which removes cause 4 entirely. The direct API call also gives you full visibility into the response from every submission attempt, so a 429 rate limit response shows up in your logs rather than disappearing silently.&lt;/p&gt;

&lt;p&gt;For high-volume forms, the direct API approach also makes it easier to implement your own retry logic around the Sheets write, which the dedicated plugin does not expose.&lt;/p&gt;

&lt;h2&gt;
  
  
  Diagnosing Which Cause You Are Hitting
&lt;/h2&gt;

&lt;p&gt;Enable WP_DEBUG and WP_DEBUG_LOG in wp-config.php if they are not already on. After your next missing submission, check wp-content/debug.log immediately for any memory, timeout, or API error entries.&lt;/p&gt;

&lt;p&gt;Check your Google Cloud Console quotas dashboard for rate limit spikes around the times submissions went missing.&lt;/p&gt;

&lt;p&gt;Temporarily disable WP Rocket's JavaScript optimisation and test again. If submissions stop going missing, WP Rocket is involved.&lt;/p&gt;

&lt;p&gt;Check the last authorisation date in the GSheetConnector plugin settings and confirm your Google Cloud app is in Production status rather than Testing.&lt;/p&gt;

</description>
      <category>wordpress</category>
      <category>googlesheets</category>
      <category>api</category>
      <category>webdev</category>
    </item>
    <item>
      <title>Why Your CF7 Google Sheets Connector Randomly Stops Working (And Re-Authorizing Doesn't Fix It)</title>
      <dc:creator>Rahul Sharma</dc:creator>
      <pubDate>Fri, 03 Jul 2026 12:37:49 +0000</pubDate>
      <link>https://dev.to/rahul_sharma_15bd129bc69e/why-your-cf7-google-sheets-connector-randomly-stops-working-and-re-authorizing-doesnt-fix-it-30n9</link>
      <guid>https://dev.to/rahul_sharma_15bd129bc69e/why-your-cf7-google-sheets-connector-randomly-stops-working-and-re-authorizing-doesnt-fix-it-30n9</guid>
      <description>&lt;p&gt;A WordPress support thread describes a pattern that shows up constantly with CF7-to-Google-Sheets connector plugins:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The connector works fine right after setup&lt;/li&gt;
&lt;li&gt;Some time later, it just stops sending data&lt;/li&gt;
&lt;li&gt;Deactivating and reactivating the plugin doesn't help&lt;/li&gt;
&lt;li&gt;Revoking Google permissions and re-granting them doesn't help&lt;/li&gt;
&lt;li&gt;Connecting to a brand new sheet from scratch has the same problem&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The plugin author's own reply in that thread points people to check &lt;code&gt;debug.log&lt;/code&gt; for authentication errors — which is the right instinct, because this almost never turns out to be a sheet permissions issue. It's an OAuth token lifecycle issue, and it's baked into how Google treats third-party apps that haven't gone through full verification.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Actual Cause: Testing Mode and the 7-Day Token Expiry
&lt;/h2&gt;

&lt;p&gt;Every plugin that connects to Google Sheets via OAuth does so through a Google Cloud project. That project has a publishing status: &lt;strong&gt;Testing&lt;/strong&gt; or &lt;strong&gt;In Production&lt;/strong&gt;. If the project is in Testing (which is the default, and where a lot of smaller plugin integrations stay), Google enforces a hard rule: refresh tokens issued to that app expire after exactly 7 days, regardless of how many times the user re-authorizes.&lt;/p&gt;

&lt;p&gt;This explains the exact symptom in the thread. The connector works after setup because the token is fresh. A week or so later it silently dies. The site owner revokes and re-grants access, which issues a brand new token — so it works again for a few days, then dies again. Because the failure is intermittent and recovers temporarily after re-auth, it looks like a bug that "sometimes" happens rather than a fixed 7-day countdown.&lt;/p&gt;

&lt;p&gt;A few related conditions produce the same &lt;code&gt;invalid_grant&lt;/code&gt; failure and are worth ruling out too:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;100-token cap&lt;/strong&gt; — each Google account can hold at most 100 live refresh tokens per OAuth client. Past that, Google silently invalidates the oldest one with no warning&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;6-month inactivity&lt;/strong&gt; — a refresh token that sits unused for six consecutive months is auto-revoked&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Password change&lt;/strong&gt; — if the connected scopes touch Gmail, a password reset revokes the token immediately&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Workspace admin policy&lt;/strong&gt; — an admin can restrict specific scopes after the fact, breaking a previously working connection&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;None of these are fixed by deactivating/reactivating the plugin, and none are fixed by connecting a new spreadsheet, since the token — not the sheet — is what's broken.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why Re-Authorizing Doesn't Actually Fix It
&lt;/h2&gt;

&lt;p&gt;Re-granting permission resets the clock, it doesn't remove the 7-day ceiling. As long as the underlying Google Cloud project stays in Testing mode, every token issued to it — old sheet or new sheet — will expire on the same schedule. Publishing the app to Production removes the 7-day limit, but for apps that use sensitive or restricted scopes, that requires completing Google's full OAuth verification process, which most small WordPress plugin setups never go through.&lt;/p&gt;

&lt;h2&gt;
  
  
  Sidestepping the Whole Problem
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://www.contactformtoapi.com/" rel="noopener noreferrer"&gt;Contact Form to API&lt;/a&gt; doesn't authenticate to Google Sheets the way an OAuth connector does, which means the 7-day/100-token/6-month expiry rules that govern user-consent tokens don't apply at all. There are two ways to wire it up:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Option 1 — Google Apps Script Web App (fastest to set up)&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Deploy a small Apps Script bound to your sheet as a Web App, set to "Execute as: Me" and "Anyone can access":&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;doPost&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;sheet&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;SpreadsheetApp&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getActiveSpreadsheet&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;getSheetByName&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Sheet1&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;parse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;postData&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;contents&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nx"&gt;sheet&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;appendRow&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;email&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;message&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;ContentService&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;createTextOutput&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;status&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;ok&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;}))&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setMimeType&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;ContentService&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;MimeType&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Authorization here happens once, at deployment time, under your own Google account — there's no separate per-request OAuth handshake and no refresh token to expire. Point Contact Form to API's request URL at the deployment's &lt;code&gt;/exec&lt;/code&gt; URL, map your CF7 fields into the JSON body, and submissions land in the sheet directly.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Option 2 — Sheets API v4 with a service account (more robust)&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;If you'd rather call Google's real API instead of routing through Apps Script, create a service account in Google Cloud, share the target sheet with the service account's email, and authenticate requests with a JWT signed by the service account key:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight http"&gt;&lt;code&gt;&lt;span class="err"&gt;POST https://sheets.googleapis.com/v4/spreadsheets/{spreadsheetId}/values/Sheet1!A1:append?valueInputOption=RAW
Authorization: Bearer {service_account_access_token}
Content-Type: application/json

{
  "values": [["2026-07-03", "Jane Doe", "jane@example.com", "Hello"]]
}
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Service account credentials aren't part of the end-user consent lifecycle at all, so none of the Testing-mode expiry rules apply to them.&lt;/p&gt;

&lt;h2&gt;
  
  
  Troubleshooting Checklist
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Check whether the connector's Google Cloud project is in Testing or Production status&lt;/li&gt;
&lt;li&gt;If you have access to the project, check the refresh token count against the 100-token cap&lt;/li&gt;
&lt;li&gt;Confirm the connected scopes don't include Gmail if a password was recently changed&lt;/li&gt;
&lt;li&gt;If moving to a direct API/Apps Script approach, confirm the sheet is shared with the correct account (your own account for Apps Script, the service account email for the Sheets API)&lt;/li&gt;
&lt;li&gt;Watch for &lt;code&gt;invalid_grant&lt;/code&gt; specifically in logs — that error code, not a generic failure, confirms it's a token issue rather than a sheet permissions issue&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;If your CF7 integration keeps dying every week or so despite reconnecting, don't keep re-authorizing — check the publishing status of whatever Google Cloud project sits behind the connector, or skip the OAuth layer entirely with &lt;a href="https://www.contactformtoapi.com/" rel="noopener noreferrer"&gt;Contact Form to API&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>wordpress</category>
      <category>googleapi</category>
      <category>api</category>
      <category>webdev</category>
    </item>
    <item>
      <title>CF7 + Sendinblue: Contacts Going to "All Contacts" Instead of Your Specific List</title>
      <dc:creator>Rahul Sharma</dc:creator>
      <pubDate>Wed, 01 Jul 2026 08:10:16 +0000</pubDate>
      <link>https://dev.to/rahul_sharma_15bd129bc69e/cf7-sendinblue-contacts-going-to-all-contacts-instead-of-your-specific-list-3214</link>
      <guid>https://dev.to/rahul_sharma_15bd129bc69e/cf7-sendinblue-contacts-going-to-all-contacts-instead-of-your-specific-list-3214</guid>
      <description>&lt;p&gt;A site owner posted on the WordPress forums with a problem that had been going on for months. Their CF7 form was connected to Sendinblue (now called Brevo) using CF7's built-in integration. Every time someone submitted the form, a new contact appeared in Sendinblue. But it always landed in the general "All Contacts" pool, never in the specific list they had selected in the plugin settings. The welcome email also never fired.&lt;/p&gt;

&lt;p&gt;The CF7 plugin author said: "Necessary fields are missing in your form."&lt;/p&gt;

&lt;p&gt;The site owner did not understand what that meant. Neither did a second person who replied months later with the exact same problem. The thread was closed with the issue unresolved for both of them.&lt;/p&gt;

&lt;p&gt;This post explains what "necessary fields are missing" actually means, why the built-in integration behaves this way, and how to get contacts reliably into the right Sendinblue list every time.&lt;/p&gt;

&lt;h2&gt;
  
  
  What "Necessary Fields Are Missing" Actually Means
&lt;/h2&gt;

&lt;p&gt;CF7's built-in Sendinblue integration has a step called &lt;strong&gt;Contact Attribute to Form-Field Mapping&lt;/strong&gt;. This is where you tell CF7 which form field corresponds to which Sendinblue contact attribute.&lt;/p&gt;

&lt;p&gt;The critical field is the email address. Even if your CF7 form has an email field, CF7's built-in Sendinblue integration does not automatically know that your email field should map to Sendinblue's &lt;code&gt;EMAIL&lt;/code&gt; attribute. You have to map it explicitly in the integration settings.&lt;/p&gt;

&lt;p&gt;Here is what happens when the email field is not mapped:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Sendinblue receives a contact creation request&lt;/li&gt;
&lt;li&gt;Because no email attribute is explicitly provided in the mapped fields, Sendinblue creates a generic "identified contact" without a proper email&lt;/li&gt;
&lt;li&gt;Without a valid email, Sendinblue cannot add the contact to a specific list — lists require identifiable contacts&lt;/li&gt;
&lt;li&gt;The contact lands in the general pool instead of your desired list&lt;/li&gt;
&lt;li&gt;The welcome email never fires because there is no valid email to send to&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The fix for CF7's built-in integration is to go to CF7 Settings, open the Sendinblue tab, find the Contact Attribute to Form-Field Mapping section, and add a row that maps &lt;code&gt;EMAIL&lt;/code&gt; to your form's email field name (e.g. &lt;code&gt;your-email&lt;/code&gt;). Save the settings and test again.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why This Keeps Tripping People Up
&lt;/h2&gt;

&lt;p&gt;The CF7 Sendinblue integration UI has a separate section for selecting which list contacts should be added to. It is reasonable to assume that selecting a list there is all you need to do. The mapping step feels like optional customisation, not a required prerequisite for the list assignment to work.&lt;/p&gt;

&lt;p&gt;But the list assignment depends on the contact having a valid, recognisable email. If the mapping is not set, the email is not in the payload in the way Sendinblue expects it, the contact is created without it, and the list assignment silently fails.&lt;/p&gt;

&lt;p&gt;This is a documentation gap, not a bug. But the effect is the same: contacts go to the wrong place and the site owner has no visible error to diagnose.&lt;/p&gt;

&lt;h2&gt;
  
  
  The More Reliable Fix: Connect Directly to Brevo's API
&lt;/h2&gt;

&lt;p&gt;CF7's built-in Sendinblue integration has multiple settings that have to be configured in exactly the right way before contacts land in the right list. If any one piece is missing, the whole thing silently fails.&lt;/p&gt;

&lt;p&gt;A more direct approach is to call Brevo's API yourself with &lt;a href="https://www.contactformtoapi.com/brevo-integration-with-contact-form-to-any-api/" rel="noopener noreferrer"&gt;Contact Form to API&lt;/a&gt;. You send a POST request to Brevo's contacts endpoint and include the &lt;code&gt;listIds&lt;/code&gt; array directly in the request body. There is no hidden mapping requirement. The list ID goes in the payload you control, so you always know exactly which list the contact will land in.&lt;/p&gt;

&lt;p&gt;The Brevo API endpoint for this is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight http"&gt;&lt;code&gt;&lt;span class="err"&gt;POST https://api.brevo.com/v3/contacts
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;With a body like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"email"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"person@example.com"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"attributes"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"FIRSTNAME"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Jane"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"LASTNAME"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Smith"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"listIds"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"updateEnabled"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;listIds&lt;/code&gt; array is where you put your Brevo list ID. The &lt;code&gt;updateEnabled: true&lt;/code&gt; flag means existing contacts get updated rather than rejected, which avoids errors for repeat form submitters.&lt;/p&gt;

&lt;p&gt;You find your list ID in Brevo under Contacts, Lists, and then opening the specific list. The ID appears in the URL.&lt;/p&gt;

&lt;h2&gt;
  
  
  What to Do If the Built-In Integration Fix Does Not Work
&lt;/h2&gt;

&lt;p&gt;If you have added the email mapping and contacts are still landing in the wrong list, check these things.&lt;/p&gt;

&lt;p&gt;Confirm your list ID in CF7's settings matches the actual list ID in Sendinblue. It is easy to copy the wrong number if you have multiple lists.&lt;/p&gt;

&lt;p&gt;Check whether the Sendinblue list you selected requires double opt-in confirmation. If double opt-in is enabled on the list, contacts are placed in a pending state until they confirm their email. They will not appear in the list until they click the confirmation link in the email Sendinblue sends them.&lt;/p&gt;

&lt;p&gt;Check whether the welcome email setting in CF7 is actually tied to a Sendinblue automation or transactional template. As covered in a related post, the welcome email checkbox in CF7's Sendinblue settings has unreliable behaviour and may not trigger a transactional template send.&lt;/p&gt;

&lt;h2&gt;
  
  
  Summary
&lt;/h2&gt;

&lt;p&gt;Contacts landing in Sendinblue's general pool instead of your specific list almost always means the email field is not mapped in CF7's contact attribute mapping settings. The fix is to explicitly map &lt;code&gt;EMAIL&lt;/code&gt; to your form's email field in the integration settings.&lt;/p&gt;

&lt;p&gt;For a more reliable setup that gives you full control over which list receives the contact and exactly what data is sent, calling Brevo's contacts API directly through Contact Form to API removes the silent failure risk entirely.&lt;/p&gt;

</description>
      <category>wordpress</category>
      <category>emailmarketing</category>
      <category>api</category>
      <category>webdev</category>
    </item>
    <item>
      <title>CF7 Submissions Not Reaching Klaviyo? Here Is What Is Actually Going On</title>
      <dc:creator>Rahul Sharma</dc:creator>
      <pubDate>Tue, 30 Jun 2026 08:09:54 +0000</pubDate>
      <link>https://dev.to/rahul_sharma_15bd129bc69e/cf7-submissions-not-reaching-klaviyo-here-is-what-is-actually-going-on-lh</link>
      <guid>https://dev.to/rahul_sharma_15bd129bc69e/cf7-submissions-not-reaching-klaviyo-here-is-what-is-actually-going-on-lh</guid>
      <description>&lt;p&gt;A site owner posted on the WordPress forums that their signup form was not sending data to Klaviyo. The API key was correct. The list ID was correct. Everything looked set up properly, but submissions were not showing up.&lt;/p&gt;

&lt;p&gt;The plugin author asked a clarifying question that turned out to be the key to the whole problem: did the form fail to submit with an error message, or did it submit successfully but the new contact never appeared in the Klaviyo list? Those are two completely different problems with two completely different fixes.&lt;/p&gt;

&lt;p&gt;The site owner eventually found a setting that was wrong and fixed it themselves, but never said which one. This post covers every place a CF7 to Klaviyo integration can break, what each failure looks like, and how to fix it.&lt;/p&gt;

&lt;h2&gt;
  
  
  The First Thing to Check: List vs Campaign
&lt;/h2&gt;

&lt;p&gt;This is the single most common confusion with Klaviyo integrations and it is worth ruling out before anything else.&lt;/p&gt;

&lt;p&gt;When your CF7 form adds someone to Klaviyo, it only adds them to a &lt;strong&gt;list&lt;/strong&gt;. It does not enroll them in a &lt;strong&gt;campaign&lt;/strong&gt;. These are two separate things in Klaviyo. A list is a static collection of contacts. A campaign is an email that gets sent out, either immediately or on a schedule.&lt;/p&gt;

&lt;p&gt;If you check your Klaviyo list and the new contact is sitting there correctly, your integration is working. If you were expecting them to receive an automatic welcome email and nothing arrived, that is a separate Klaviyo flow setup issue, not an integration issue. You need a Klaviyo flow that triggers when someone is added to that specific list, and that flow needs to be turned on. Check Klaviyo under Flows and confirm a "List Subscribed" trigger flow exists and is live, not in draft.&lt;/p&gt;

&lt;p&gt;If the contact is not even appearing in the list at all, the problem is somewhere in the API call itself. Keep reading.&lt;/p&gt;

&lt;h2&gt;
  
  
  Reason 1: Using the Wrong Klaviyo API Version
&lt;/h2&gt;

&lt;p&gt;Klaviyo has deprecated several older API endpoints over the past few years. If your integration was set up a while ago, or if a tutorial you followed was written before the changes, you might be using endpoints that no longer work the way you expect.&lt;/p&gt;

&lt;p&gt;The old &lt;code&gt;v1/v2&lt;/code&gt; endpoints like &lt;code&gt;POST /api/v2/list/{list_id}/subscribe&lt;/code&gt; are deprecated. If your plugin or custom code is still using them, the request can fail silently or return an error that your integration is not surfacing to you.&lt;/p&gt;

&lt;p&gt;The current correct approach uses two separate API calls. First you create or update the profile:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight http"&gt;&lt;code&gt;&lt;span class="err"&gt;POST https://a.klaviyo.com/api/profiles/
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then you add that profile to a list, or better yet, subscribe them with consent:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight http"&gt;&lt;code&gt;&lt;span class="err"&gt;POST https://a.klaviyo.com/api/profile-subscription-bulk-create-jobs/
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This second endpoint is what Klaviyo recommends if you want the contact's consent properly recorded, rather than just silently dropping them into a list with no marketing consent on record. If your form is a signup form meant for email campaigns, you want consent recorded correctly. Otherwise Klaviyo will treat the contact as not subscribed even though they appear in your list.&lt;/p&gt;

&lt;h2&gt;
  
  
  Reason 2: Authentication Header Format Is Wrong
&lt;/h2&gt;

&lt;p&gt;Klaviyo's API requires the Authorization header in a very specific format:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight http"&gt;&lt;code&gt;&lt;span class="err"&gt;Authorization: Klaviyo-API-Key pk_your_private_key_here
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A common mistake is using &lt;code&gt;Bearer&lt;/code&gt; instead of &lt;code&gt;Klaviyo-API-Key&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight http"&gt;&lt;code&gt;&lt;span class="err"&gt;Authorization: Bearer pk_your_private_key_here
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This looks correct because Bearer is the standard format for most APIs, but Klaviyo specifically requires its own prefix. Using the wrong prefix returns a 401 authentication error. If your integration is silently failing and you have not checked the raw error response, this is one of the first things to verify.&lt;/p&gt;

&lt;p&gt;You also need the API revision header on every request:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;revision&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;2024-10-15&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Klaviyo versions its API by date. Missing this header can cause requests to behave unpredictably or get rejected depending on which default revision your account falls back to.&lt;/p&gt;

&lt;h2&gt;
  
  
  Reason 3: Missing Required Identifier on the Profile
&lt;/h2&gt;

&lt;p&gt;Klaviyo requires at least one identifier to create a profile: an email address, a phone number, or an external ID. If your CF7 form only collects a phone number and your integration sends the request without an email and without marking the phone number correctly, Klaviyo returns:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"detail"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"One or more identifiers is required"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Check what fields your CF7 form actually sends to Klaviyo. If the form has an email field, make sure it is actually included in the payload and not accidentally left out of the field mapping.&lt;/p&gt;

&lt;h2&gt;
  
  
  Reason 4: Profile Already Exists (409 Conflict)
&lt;/h2&gt;

&lt;p&gt;If someone has previously interacted with your Klaviyo account under the same email, trying to create them again as a brand new profile can return a 409 conflict:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"detail"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"A profile already exists with one of these identifiers"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This does not mean your integration is broken. It means Klaviyo already has a profile for that person somewhere in your account, even if they are not in the specific list you are checking. The correct behaviour is to update the existing profile rather than attempting to create a new one. Using the subscribe/bulk job endpoint mentioned above handles this correctly because it performs an upsert: it updates the profile if it exists, or creates it if it does not.&lt;/p&gt;

&lt;h2&gt;
  
  
  How to Set This Up Correctly Without Managing API Versions Yourself
&lt;/h2&gt;

&lt;p&gt;Klaviyo's API has changed enough times that hand-rolled integrations or older plugins frequently break without obvious warning. The safest way to connect Contact Form 7 to Klaviyo reliably is to use a tool that calls Klaviyo's current API endpoints correctly and gives you visibility into what is actually being sent and received.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.contactformtoapi.com/" rel="noopener noreferrer"&gt;Contact Form to API&lt;/a&gt; lets you set up a Contact Form 7 to Klaviyo integration by configuring the exact Klaviyo endpoint, adding your API key with the correct &lt;code&gt;Klaviyo-API-Key&lt;/code&gt; header format, and mapping your CF7 form fields to the profile attributes Klaviyo expects. You also get visibility into the response from each submission, so if something does fail, you see the actual error from Klaviyo instead of guessing why a contact never showed up.&lt;/p&gt;

&lt;p&gt;This removes the guesswork around API versions, header formats, and consent handling that trips up most manual CF7 to Klaviyo setups.&lt;/p&gt;

&lt;h2&gt;
  
  
  Quick Checklist
&lt;/h2&gt;

&lt;p&gt;If your CF7 form is not sending data to Klaviyo, work through these in order.&lt;/p&gt;

&lt;p&gt;Check whether the contact actually appears in your Klaviyo list. If they do, your integration works and the issue is with a flow or campaign not being triggered, which is a separate Klaviyo setup task.&lt;/p&gt;

&lt;p&gt;Confirm you are using current API endpoints, not the deprecated v1/v2 versions.&lt;/p&gt;

&lt;p&gt;Check your Authorization header uses &lt;code&gt;Klaviyo-API-Key&lt;/code&gt;, not &lt;code&gt;Bearer&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Confirm your CF7 form sends at least one valid identifier (email, phone, or external ID) to Klaviyo.&lt;/p&gt;

&lt;p&gt;If you see a 409 conflict error, switch to the subscribe/bulk job endpoint which performs an upsert instead of a strict create.&lt;/p&gt;

</description>
      <category>wordpress</category>
      <category>emailmarketing</category>
      <category>api</category>
      <category>webdev</category>
    </item>
    <item>
      <title>CF7 + Campaign Monitor: "Failed to Deserialize" 400 Error on Unchecked Checkbox</title>
      <dc:creator>Rahul Sharma</dc:creator>
      <pubDate>Mon, 29 Jun 2026 08:55:57 +0000</pubDate>
      <link>https://dev.to/rahul_sharma_15bd129bc69e/cf7-campaign-monitor-failed-to-deserialize-400-error-on-unchecked-checkbox-3bbh</link>
      <guid>https://dev.to/rahul_sharma_15bd129bc69e/cf7-campaign-monitor-failed-to-deserialize-400-error-on-unchecked-checkbox-3bbh</guid>
      <description>&lt;p&gt;A developer posted on the WordPress forums with a very specific and frustrating bug. Their CF7 form connected to Campaign Monitor worked perfectly when the newsletter checkbox was ticked. The moment someone left it unticked, the integration threw a 400 error:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"Code"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;400&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"Message"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Failed to deserialize your request.&lt;/span&gt;&lt;span class="se"&gt;\r\n&lt;/span&gt;&lt;span class="s2"&gt;Please check the documentation and try again.&lt;/span&gt;&lt;span class="se"&gt;\r\n&lt;/span&gt;&lt;span class="s2"&gt;Fields in error: subscriber"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The plugin team fixed it in their next update. But the underlying reason this happens is worth understanding because the same bug will hit any CF7-to-Campaign Monitor integration that maps a checkbox to the "Authorized to send" field.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why an Unchecked Checkbox Breaks the API Call
&lt;/h2&gt;

&lt;p&gt;The CF7 form had a newsletter opt-in checkbox:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;[checkbox add-to-nl use_label_element "accept newsletter"]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This was mapped to Campaign Monitor's &lt;code&gt;Authorized to send&lt;/code&gt; field, which controls whether Campaign Monitor can send marketing emails to the subscriber.&lt;/p&gt;

&lt;p&gt;When the checkbox is ticked, CF7 sends the value &lt;code&gt;"accept newsletter"&lt;/code&gt; for the &lt;code&gt;add-to-nl&lt;/code&gt; field. The integration plugin translates this to &lt;code&gt;true&lt;/code&gt; and Campaign Monitor accepts it.&lt;/p&gt;

&lt;p&gt;When the checkbox is left unticked, CF7 sends nothing for &lt;code&gt;add-to-nl&lt;/code&gt;. There is no value because the user did not select anything. The integration plugin then either sends &lt;code&gt;null&lt;/code&gt;, an empty string, or skips the field entirely.&lt;/p&gt;

&lt;p&gt;Campaign Monitor's &lt;code&gt;Authorized to send&lt;/code&gt; field expects a boolean: &lt;code&gt;true&lt;/code&gt; or &lt;code&gt;false&lt;/code&gt;. It cannot accept null, empty string, or a missing value. When it receives anything other than a proper boolean, it cannot parse the request and returns the deserialization error.&lt;/p&gt;

&lt;p&gt;This is not a CF7 bug and it is not a Campaign Monitor bug. It is a gap in how the integration plugin handled the empty checkbox case. A ticked checkbox means &lt;code&gt;true&lt;/code&gt;. An unticked checkbox means &lt;code&gt;false&lt;/code&gt;. The plugin needed to explicitly convert the empty value to &lt;code&gt;false&lt;/code&gt; before sending it to Campaign Monitor.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Fix: Always Send a Boolean, Never Send Empty
&lt;/h2&gt;

&lt;p&gt;The correct behaviour for any integration mapping a CF7 checkbox to a Campaign Monitor boolean field is to evaluate the checkbox state and send &lt;code&gt;true&lt;/code&gt; or &lt;code&gt;false&lt;/code&gt; explicitly.&lt;/p&gt;

&lt;p&gt;If you are writing a custom integration, here is how to handle it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="nf"&gt;add_action&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'wpcf7_before_send_mail'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'cf7_to_campaign_monitor'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;cf7_to_campaign_monitor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$contact_form&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="n"&gt;int&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="nv"&gt;$contact_form&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;id&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="no"&gt;YOUR_FORM_ID&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="nv"&gt;$submission&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;WPCF7_Submission&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;get_instance&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nv"&gt;$submission&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="nv"&gt;$data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$submission&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;get_posted_data&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

    &lt;span class="c1"&gt;// Checkbox returns an array of selected values when ticked,&lt;/span&gt;
    &lt;span class="c1"&gt;// or an empty array when unticked&lt;/span&gt;
    &lt;span class="nv"&gt;$newsletter_checked&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="k"&gt;empty&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$data&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'add-to-nl'&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt;

    &lt;span class="nv"&gt;$api_key&lt;/span&gt;  &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;defined&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'CM_API_KEY'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;?&lt;/span&gt; &lt;span class="no"&gt;CM_API_KEY&lt;/span&gt; &lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;''&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nv"&gt;$list_id&lt;/span&gt;  &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;defined&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'CM_LIST_ID'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;?&lt;/span&gt; &lt;span class="no"&gt;CM_LIST_ID&lt;/span&gt; &lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;''&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="nv"&gt;$response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;wp_remote_post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="s2"&gt;"https://api.createsend.com/api/v3.3/subscribers/&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nv"&gt;$list_id&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;.json"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;[&lt;/span&gt;
            &lt;span class="s1"&gt;'headers'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
                &lt;span class="s1"&gt;'Authorization'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'Basic '&lt;/span&gt; &lt;span class="mf"&gt;.&lt;/span&gt; &lt;span class="nb"&gt;base64_encode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$api_key&lt;/span&gt; &lt;span class="mf"&gt;.&lt;/span&gt; &lt;span class="s1"&gt;':x'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
                &lt;span class="s1"&gt;'Content-Type'&lt;/span&gt;  &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'application/json'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="p"&gt;],&lt;/span&gt;
            &lt;span class="s1"&gt;'body'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;wp_json_encode&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;
                &lt;span class="s1"&gt;'EmailAddress'&lt;/span&gt;   &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;sanitize_email&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$data&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'your-email'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;??&lt;/span&gt; &lt;span class="s1"&gt;''&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
                &lt;span class="s1"&gt;'Name'&lt;/span&gt;           &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;sanitize_text_field&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$data&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'your-name'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;??&lt;/span&gt; &lt;span class="s1"&gt;''&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
                &lt;span class="s1"&gt;'ConsentToTrack'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;$newsletter_checked&lt;/span&gt; &lt;span class="o"&gt;?&lt;/span&gt; &lt;span class="s1"&gt;'yes'&lt;/span&gt; &lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'no'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="s1"&gt;'CustomFields'&lt;/span&gt;   &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
                    &lt;span class="p"&gt;[&lt;/span&gt;
                        &lt;span class="s1"&gt;'Key'&lt;/span&gt;   &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'Activity'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                        &lt;span class="s1"&gt;'Value'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;sanitize_text_field&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$data&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'activity'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;??&lt;/span&gt; &lt;span class="s1"&gt;''&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
                    &lt;span class="p"&gt;],&lt;/span&gt;
                &lt;span class="p"&gt;],&lt;/span&gt;
                &lt;span class="s1"&gt;'Resubscribe'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="p"&gt;]),&lt;/span&gt;
            &lt;span class="s1"&gt;'timeout'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;15&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;is_wp_error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$response&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nb"&gt;error_log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'[CF7-&amp;gt;CM] Error: '&lt;/span&gt; &lt;span class="mf"&gt;.&lt;/span&gt; &lt;span class="nv"&gt;$response&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;get_error_message&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The key line is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="s1"&gt;'ConsentToTrack'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;$newsletter_checked&lt;/span&gt; &lt;span class="o"&gt;?&lt;/span&gt; &lt;span class="s1"&gt;'yes'&lt;/span&gt; &lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'no'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Whether the checkbox is ticked or not, Campaign Monitor always receives a valid value. Empty is never sent.&lt;/p&gt;

&lt;p&gt;Note that Campaign Monitor's field name for this is &lt;code&gt;ConsentToTrack&lt;/code&gt; in the API, not &lt;code&gt;Authorized to send&lt;/code&gt; which is the label shown in the Campaign Monitor UI.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Same Problem Affects Any Boolean Field
&lt;/h2&gt;

&lt;p&gt;The deserialization error on an unchecked checkbox is not specific to &lt;code&gt;Authorized to send&lt;/code&gt;. It will happen on any Campaign Monitor field that expects a boolean or a specific set of allowed values when the integration sends an empty or null value instead.&lt;/p&gt;

&lt;p&gt;Common situations where this occurs:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A checkbox mapped to any Campaign Monitor custom field with a predefined set of options&lt;/li&gt;
&lt;li&gt;An acceptance field mapped to consent or tracking fields&lt;/li&gt;
&lt;li&gt;A dropdown that was left at the default empty option&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In all these cases the fix is the same: evaluate the empty state explicitly and send the correct fallback value rather than sending nothing.&lt;/p&gt;

&lt;h2&gt;
  
  
  If You Are Using an Integration Plugin
&lt;/h2&gt;

&lt;p&gt;If you are using Bit Integrations or any similar CF7-to-Campaign Monitor plugin and you are seeing this error, the first thing to do is update the plugin to the latest version. The Bit Integrations team confirmed they fixed this in an update released shortly after the forum thread.&lt;/p&gt;

&lt;p&gt;If updating does not fix it, check your field mapping screen and look at how the checkbox field is mapped. Some plugins let you set a fallback value for when a checkbox is unchecked. Setting that fallback to &lt;code&gt;false&lt;/code&gt; or &lt;code&gt;no&lt;/code&gt; depending on what Campaign Monitor expects for that field will resolve the error.&lt;/p&gt;

&lt;p&gt;If your plugin does not support fallback values for empty fields, that is a capability gap and switching to a more flexible integration tool is the right move. With &lt;a href="https://www.contactformtoapi.com/spam-free-form-automation-wordpress/" rel="noopener noreferrer"&gt;Contact Form to API&lt;/a&gt;, you can set the &lt;code&gt;Authorized to send&lt;/code&gt; field as a fixed value of &lt;code&gt;no&lt;/code&gt; in the field mapping instead of mapping it to the CF7 checkbox directly. Campaign Monitor then always receives a valid boolean regardless of whether the visitor ticks the checkbox or not, which eliminates the deserialization error entirely.&lt;/p&gt;

&lt;h2&gt;
  
  
  Quick Summary
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Scenario&lt;/th&gt;
&lt;th&gt;What is sent to Campaign Monitor&lt;/th&gt;
&lt;th&gt;Result&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Checkbox ticked&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;true&lt;/code&gt; or &lt;code&gt;"yes"&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;Success&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Checkbox unticked, no fallback&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;null&lt;/code&gt; or empty string&lt;/td&gt;
&lt;td&gt;400 deserialization error&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Checkbox unticked, fallback set&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;false&lt;/code&gt; or &lt;code&gt;"no"&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;Success&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The fix is always in the fallback. Campaign Monitor needs a boolean. Empty is not a boolean. Always send one or the other.&lt;/p&gt;

</description>
      <category>wordpress</category>
      <category>emailmarketing</category>
      <category>api</category>
      <category>webdev</category>
    </item>
    <item>
      <title>CF7 + ActiveCampaign: Why the "Account" Field Never Shows in Your Field Mapper</title>
      <dc:creator>Rahul Sharma</dc:creator>
      <pubDate>Thu, 25 Jun 2026 08:18:57 +0000</pubDate>
      <link>https://dev.to/rahul_sharma_15bd129bc69e/cf7-activecampaign-why-the-account-field-never-shows-in-your-field-mapper-ne4</link>
      <guid>https://dev.to/rahul_sharma_15bd129bc69e/cf7-activecampaign-why-the-account-field-never-shows-in-your-field-mapper-ne4</guid>
      <description>&lt;p&gt;A developer posted on the WordPress support forums that their CF7 to ActiveCampaign integration was missing the "Account" field in the mapping section. They could see the Account field clearly in their ActiveCampaign account. It just never appeared in the plugin's field mapper.&lt;/p&gt;

&lt;p&gt;The plugin team said it was under development and would be released next week. The developer waited five weeks. Then posted again. The field was still missing. The thread was closed.&lt;/p&gt;

&lt;p&gt;This is not a bug that will be fixed by waiting for a plugin update. The reason the Account field is missing from CF7 integration mappers is a fundamental difference in how ActiveCampaign structures its data.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why the Account Field Is Different From Other Fields
&lt;/h2&gt;

&lt;p&gt;In ActiveCampaign, a Contact and an Account are two separate types of records.&lt;/p&gt;

&lt;p&gt;A Contact is a person: name, email, phone, custom contact fields. When your CF7 form submits and the plugin calls ActiveCampaign's API, it calls the Contacts endpoint and creates or updates a person record.&lt;/p&gt;

&lt;p&gt;An Account in ActiveCampaign is a company record. It has its own fields: company name, website, industry, annual revenue. Contacts can be linked to Accounts, but an Account is not a field on a Contact. It is a separate object with its own API endpoint.&lt;/p&gt;

&lt;p&gt;This is why the Account field does not appear in the field mapper. The plugin fetches the list of available fields from ActiveCampaign's Contact fields API. The Account field is not in that list because it is not a Contact field. It belongs to a different API endpoint entirely.&lt;/p&gt;

&lt;p&gt;Most CF7 to ActiveCampaign integration plugins only work with the Contacts endpoint. They were not built to create or link Account records, which is a separate operation.&lt;/p&gt;

&lt;h2&gt;
  
  
  What the ActiveCampaign API Actually Requires
&lt;/h2&gt;

&lt;p&gt;To create a Contact and link it to an Account in ActiveCampaign, you need two API calls.&lt;/p&gt;

&lt;p&gt;The first call creates or updates the Contact:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight http"&gt;&lt;code&gt;&lt;span class="err"&gt;POST https://youraccountname.api-us1.com/api/3/contacts
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The second call links that Contact to an Account:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight http"&gt;&lt;code&gt;&lt;span class="err"&gt;POST https://youraccountname.api-us1.com/api/3/accountContacts
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The second call takes the Contact ID returned from the first call and the Account ID from ActiveCampaign. It creates the relationship between the two records.&lt;/p&gt;

&lt;p&gt;If the Account does not exist yet, you first need to create it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight http"&gt;&lt;code&gt;&lt;span class="err"&gt;POST https://youraccountname.api-us1.com/api/3/accounts
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is three API calls chained together, with IDs being passed from one response into the next request. That is a multi-step flow that most basic CF7 integration plugins were never designed to handle.&lt;/p&gt;

&lt;h2&gt;
  
  
  How to Actually Send Company Name to ActiveCampaign From CF7
&lt;/h2&gt;

&lt;p&gt;There are two practical approaches depending on your situation.&lt;/p&gt;

&lt;p&gt;The first approach is to store the company name as a custom Contact field in ActiveCampaign rather than as an Account. In your ActiveCampaign account, go to Settings then Custom Fields and create a new field called "Company" on the Contact object. This field will appear in your plugin's field mapper because it is a Contact field. Your CF7 form can map the company name field to it and it will be stored on the Contact record.&lt;/p&gt;

&lt;p&gt;The downside is that this is not a proper ActiveCampaign Account record. It is just a text field on the Contact. If your team needs to use ActiveCampaign's Account features like account-level automation or company scoring, this workaround will not satisfy that requirement.&lt;/p&gt;

&lt;p&gt;The second approach is to use a direct API integration that supports multi-step calls. &lt;a href="https://www.contactformtoapi.com/contact-form-7-activecampaign-webhook-integration/" rel="noopener noreferrer"&gt;Contact Form to API&lt;/a&gt; gives you control over the exact API endpoint and payload structure. You can configure it to call the ActiveCampaign Contacts endpoint with the person's details from your form. For the Account linking step, the Pro version supports chained API calls where the response from the first call is used in the second call.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Faster Workaround: Use ActiveCampaign's Webhook Instead
&lt;/h2&gt;

&lt;p&gt;If you need Account data in ActiveCampaign from CF7 submissions and do not want to build a custom PHP solution, you can use ActiveCampaign's own automation features as the middle layer.&lt;/p&gt;

&lt;p&gt;Set up your CF7 integration to create the Contact record with whatever fields your plugin supports. Then create an ActiveCampaign automation that triggers when a new Contact is created. The automation can look up or create an Account based on the company name field you stored on the Contact, then link the Contact to that Account.&lt;/p&gt;

&lt;p&gt;This pushes the Account logic into ActiveCampaign's automation engine where it belongs, and keeps your CF7 integration simple.&lt;/p&gt;

&lt;h2&gt;
  
  
  If You Need to Write This in PHP
&lt;/h2&gt;

&lt;p&gt;For developers who want to own the full integration, here is the sequence of API calls:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="nf"&gt;add_action&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'wpcf7_before_send_mail'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'cf7_to_activecampaign_with_account'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;cf7_to_activecampaign_with_account&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$contact_form&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="n"&gt;int&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="nv"&gt;$contact_form&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;id&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="no"&gt;YOUR_FORM_ID&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="nv"&gt;$submission&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;WPCF7_Submission&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;get_instance&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nv"&gt;$submission&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="nv"&gt;$data&lt;/span&gt;    &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$submission&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;get_posted_data&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="nv"&gt;$api_key&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;defined&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'AC_API_KEY'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;?&lt;/span&gt; &lt;span class="no"&gt;AC_API_KEY&lt;/span&gt; &lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;''&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nv"&gt;$api_url&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;defined&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'AC_API_URL'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;?&lt;/span&gt; &lt;span class="no"&gt;AC_API_URL&lt;/span&gt; &lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;''&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// e.g. https://yourname.api-us1.com/api/3&lt;/span&gt;

    &lt;span class="nv"&gt;$headers&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
        &lt;span class="s1"&gt;'Api-Token'&lt;/span&gt;    &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;$api_key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="s1"&gt;'Content-Type'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'application/json'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;];&lt;/span&gt;

    &lt;span class="c1"&gt;// Step 1: Create or update the Contact&lt;/span&gt;
    &lt;span class="nv"&gt;$contact_res&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;wp_remote_post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$api_url&lt;/span&gt;&lt;span class="s2"&gt;/contacts"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
        &lt;span class="s1"&gt;'headers'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;$headers&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="s1"&gt;'body'&lt;/span&gt;    &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;wp_json_encode&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;
            &lt;span class="s1"&gt;'contact'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
                &lt;span class="s1"&gt;'email'&lt;/span&gt;     &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;sanitize_email&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$data&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'your-email'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;??&lt;/span&gt; &lt;span class="s1"&gt;''&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
                &lt;span class="s1"&gt;'firstName'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;sanitize_text_field&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$data&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'your-firstname'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;??&lt;/span&gt; &lt;span class="s1"&gt;''&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
                &lt;span class="s1"&gt;'lastName'&lt;/span&gt;  &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;sanitize_text_field&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$data&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'your-lastname'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;??&lt;/span&gt; &lt;span class="s1"&gt;''&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
                &lt;span class="s1"&gt;'phone'&lt;/span&gt;     &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;sanitize_text_field&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$data&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'your-phone'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;??&lt;/span&gt; &lt;span class="s1"&gt;''&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
            &lt;span class="p"&gt;],&lt;/span&gt;
        &lt;span class="p"&gt;]),&lt;/span&gt;
        &lt;span class="s1"&gt;'timeout'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;15&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;]);&lt;/span&gt;

    &lt;span class="nv"&gt;$contact_body&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;json_decode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;wp_remote_retrieve_body&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$contact_res&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nv"&gt;$contact_id&lt;/span&gt;   &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$contact_body&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'contact'&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="s1"&gt;'id'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;??&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nv"&gt;$contact_id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="c1"&gt;// Step 2: Create the Account (or find existing by name)&lt;/span&gt;
    &lt;span class="nv"&gt;$account_res&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;wp_remote_post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$api_url&lt;/span&gt;&lt;span class="s2"&gt;/accounts"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
        &lt;span class="s1"&gt;'headers'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;$headers&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="s1"&gt;'body'&lt;/span&gt;    &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;wp_json_encode&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;
            &lt;span class="s1"&gt;'account'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
                &lt;span class="s1"&gt;'name'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;sanitize_text_field&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$data&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'your-company'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;??&lt;/span&gt; &lt;span class="s1"&gt;''&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
            &lt;span class="p"&gt;],&lt;/span&gt;
        &lt;span class="p"&gt;]),&lt;/span&gt;
        &lt;span class="s1"&gt;'timeout'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;15&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;]);&lt;/span&gt;

    &lt;span class="nv"&gt;$account_body&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;json_decode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;wp_remote_retrieve_body&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$account_res&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nv"&gt;$account_id&lt;/span&gt;   &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$account_body&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'account'&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="s1"&gt;'id'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;??&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nv"&gt;$account_id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="c1"&gt;// Step 3: Link the Contact to the Account&lt;/span&gt;
    &lt;span class="nf"&gt;wp_remote_post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$api_url&lt;/span&gt;&lt;span class="s2"&gt;/accountContacts"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
        &lt;span class="s1"&gt;'headers'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;$headers&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="s1"&gt;'body'&lt;/span&gt;    &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;wp_json_encode&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;
            &lt;span class="s1"&gt;'accountContact'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
                &lt;span class="s1"&gt;'contact'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;$contact_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="s1"&gt;'account'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;$account_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="p"&gt;],&lt;/span&gt;
        &lt;span class="p"&gt;]),&lt;/span&gt;
        &lt;span class="s1"&gt;'timeout'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;15&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;]);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Store credentials in &lt;code&gt;wp-config.php&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="nb"&gt;define&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'AC_API_KEY'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'your-activecampaign-api-key'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nb"&gt;define&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'AC_API_URL'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'https://youraccountname.api-us1.com/api/3'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Summary
&lt;/h2&gt;

&lt;p&gt;The Account field is missing from CF7 integration mappers because it is not a Contact field in ActiveCampaign. It lives on a separate Account object that requires its own API endpoint and a separate API call to link to a Contact. Most CF7 plugins only map to Contact fields. The solutions are to store company name as a custom Contact field, use ActiveCampaign automations to handle the Account linking, or use a direct API integration that supports multi-step calls.&lt;/p&gt;

</description>
      <category>wordpress</category>
      <category>activecampaign</category>
      <category>crm</category>
      <category>webdev</category>
    </item>
    <item>
      <title>CF7 to Airtable: Tables and Databases Not Loading in the Mapping Screen</title>
      <dc:creator>Rahul Sharma</dc:creator>
      <pubDate>Wed, 24 Jun 2026 08:04:38 +0000</pubDate>
      <link>https://dev.to/rahul_sharma_15bd129bc69e/cf7-to-airtable-tables-and-databases-not-loading-in-the-mapping-screen-52d7</link>
      <guid>https://dev.to/rahul_sharma_15bd129bc69e/cf7-to-airtable-tables-and-databases-not-loading-in-the-mapping-screen-52d7</guid>
      <description>&lt;p&gt;You installed the CF7 to Airtable plugin, generated a token from your Airtable account, pasted it into the plugin settings, and now you are staring at an empty dropdown where your bases and tables should appear. The plugin says it connected but nothing loads.&lt;/p&gt;

&lt;p&gt;This is exactly what one developer experienced and posted on the WordPress support forums. The fix was simple once the plugin author looked at the screenshots: the token was too short. It was either truncated when it was copied or the wrong type of token was used.&lt;/p&gt;

&lt;p&gt;But there is more to this problem than just copy-paste errors. This post covers the full picture so you get your CF7 to Airtable integration working correctly the first time.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Token Type Problem: Legacy API Key vs Personal Access Token
&lt;/h2&gt;

&lt;p&gt;Airtable deprecated their legacy API keys in early 2024. If you have used Airtable for a while and have an old connection somewhere, you might have a legacy API key that looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;keyXXXXXXXXXXXXXX
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That format no longer works. Airtable now uses Personal Access Tokens (PATs). A correctly formatted PAT looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;patXXXXXXXXXXXXXX.XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It starts with &lt;code&gt;pat&lt;/code&gt;, has a 17-character identifier, a dot, and then a long 64-character string. The full token is much longer than the legacy API key. If the token you pasted into the plugin is short or does not start with &lt;code&gt;pat&lt;/code&gt;, that is why your tables are not loading.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Scope Problem: Token Has No Permission to Read Bases
&lt;/h2&gt;

&lt;p&gt;Even with a correctly formatted PAT, your tables will still not appear if the token does not have the right scopes assigned to it.&lt;/p&gt;

&lt;p&gt;When you create a Personal Access Token in Airtable, you choose which permissions it has. The minimum scopes needed for a CF7 plugin to list your bases and tables are:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;data.records:write&lt;/code&gt; so it can create new records from form submissions&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;schema.bases:read&lt;/code&gt; so it can read the list of your bases and tables&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If &lt;code&gt;schema.bases:read&lt;/code&gt; is missing, the plugin authenticates successfully but when it tries to fetch the list of your bases to populate the dropdown, Airtable returns an empty or forbidden response. From the plugin's side it looks like there are no bases. From your side it looks like the integration is broken.&lt;/p&gt;

&lt;p&gt;Go back to your Airtable token settings at &lt;code&gt;airtable.com/create/tokens&lt;/code&gt;, find the token you are using, and check which scopes are assigned. Add &lt;code&gt;schema.bases:read&lt;/code&gt; if it is missing.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Access Problem: Token Cannot See the Right Bases
&lt;/h2&gt;

&lt;p&gt;The third reason tables do not load is that the token's access is not set to include the base you want to use.&lt;/p&gt;

&lt;p&gt;When creating a PAT, you choose which bases it can access. You can set it to "All current and future bases" or restrict it to specific bases. If you restricted it and the base you want to connect to was not included, the plugin will not be able to see it.&lt;/p&gt;

&lt;p&gt;Check the access settings on your token and make sure the base you want appears in the list, or change the access to "All bases" if you want the plugin to show everything.&lt;/p&gt;

&lt;h2&gt;
  
  
  How to Generate a Token That Actually Works
&lt;/h2&gt;

&lt;p&gt;Go to &lt;code&gt;airtable.com/create/tokens&lt;/code&gt; and create a new token. Give it a descriptive name like "WordPress CF7 Integration" so you can find it easily later.&lt;/p&gt;

&lt;p&gt;Under Scopes, add at minimum:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;data.records:write&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;schema.bases:read&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Under Access, either select "All current and future bases" or choose the specific base your CF7 form should write to.&lt;/p&gt;

&lt;p&gt;Click Create Token and copy the full token immediately. Airtable only shows you the complete token once. If you close the screen without copying it, you will need to regenerate it. The token starts with &lt;code&gt;pat&lt;/code&gt; and is quite long. Copy all of it.&lt;/p&gt;

&lt;p&gt;Paste the complete token into the plugin settings. If the bases and tables now appear in the dropdown, the token is working correctly.&lt;/p&gt;

&lt;h2&gt;
  
  
  Testing the Token Before Connecting the Plugin
&lt;/h2&gt;

&lt;p&gt;If you want to confirm your token is valid before touching the plugin settings, you can test it with a quick API call. Open your browser's address bar or use any API testing tool and call:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight http"&gt;&lt;code&gt;&lt;span class="err"&gt;GET https://api.airtable.com/v0/meta/bases
Authorization: Bearer YOUR_PAT_TOKEN
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If the token is valid and has the right scopes, you get back a JSON object listing your bases with their IDs and names. If you get a 401 or 403 response, the token has an issue with either its validity or its scopes.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Alternative: Connect CF7 Directly to Airtable's API
&lt;/h2&gt;

&lt;p&gt;Dedicated CF7 to Airtable plugins require a specific token format and scope setup that varies by plugin version and Airtable API changes. When Airtable updates something, the plugin may stop working until it is updated too.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.contactformtoapi.com/contact-form-7-third-party-integration-guide/" rel="noopener noreferrer"&gt;Contact Form to API&lt;/a&gt; connects CF7 directly to Airtable's REST API endpoint. You configure the specific Airtable base URL, add your PAT as a Bearer authorization header, and map your CF7 fields to the Airtable column names. Because you are calling the API directly with the exact endpoint and field names, there is no middleware layer that can break when Airtable changes their token format.&lt;/p&gt;

&lt;h2&gt;
  
  
  Quick Checklist
&lt;/h2&gt;

&lt;p&gt;If your tables are not loading in the CF7 Airtable plugin:&lt;/p&gt;

&lt;p&gt;Check that your token starts with &lt;code&gt;pat&lt;/code&gt; and is the full length. A short token or one starting with &lt;code&gt;key&lt;/code&gt; will not work.&lt;/p&gt;

&lt;p&gt;Check the token scopes include &lt;code&gt;schema.bases:read&lt;/code&gt; and &lt;code&gt;data.records:write&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Check the token access includes the base you want to use.&lt;/p&gt;

&lt;p&gt;Regenerate the token if you are unsure whether you copied it completely the first time.&lt;/p&gt;

&lt;p&gt;Test the token directly against &lt;code&gt;api.airtable.com/v0/meta/bases&lt;/code&gt; to confirm it works before blaming the plugin&lt;/p&gt;

</description>
      <category>wordpress</category>
      <category>airtable</category>
      <category>api</category>
      <category>webdev</category>
    </item>
    <item>
      <title>CF7 Brevo Integration Adds Contacts But Never Sends the Welcome Email - Here Is Why</title>
      <dc:creator>Rahul Sharma</dc:creator>
      <pubDate>Mon, 22 Jun 2026 08:20:50 +0000</pubDate>
      <link>https://dev.to/rahul_sharma_15bd129bc69e/cf7-brevo-integration-adds-contacts-but-never-sends-the-welcome-email-here-is-why-1e18</link>
      <guid>https://dev.to/rahul_sharma_15bd129bc69e/cf7-brevo-integration-adds-contacts-but-never-sends-the-welcome-email-here-is-why-1e18</guid>
      <description>&lt;p&gt;A site owner posted on the WordPress support forums with a problem that was driving them crazy. Their CF7 form was connected to Brevo using CF7's built-in integration. Contacts were being added to the Brevo list perfectly after every form submission. But the welcome email was never being sent. Brevo support confirmed they were receiving no email send request at all from the website.&lt;/p&gt;

&lt;p&gt;The CF7 plugin author suggested deactivating all plugins and switching to the default theme. On a live production site, that is not a realistic option. The thread was left unresolved.&lt;/p&gt;

&lt;p&gt;The real reason this happens has nothing to do with plugin conflicts. It is a gap in what CF7's built-in Brevo integration actually does.&lt;/p&gt;

&lt;h2&gt;
  
  
  What CF7's Built-In Brevo Integration Actually Does
&lt;/h2&gt;

&lt;p&gt;CF7 added native Brevo integration as a built-in feature. When you configure it and a form is submitted, CF7 makes an API call to Brevo to add the contact to your selected list.&lt;/p&gt;

&lt;p&gt;That API call goes to Brevo's contacts endpoint:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight http"&gt;&lt;code&gt;&lt;span class="err"&gt;POST https://api.brevo.com/v3/contacts
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This call creates or updates the contact in your Brevo account and adds them to the specified list. It works. The site owner confirmed contacts were appearing in Brevo correctly.&lt;/p&gt;

&lt;p&gt;But sending a welcome email using a transactional template is a completely different operation. It requires a separate API call to Brevo's transactional email endpoint:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight http"&gt;&lt;code&gt;&lt;span class="err"&gt;POST https://api.brevo.com/v3/smtp/email
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This second call does not happen. CF7's built-in Brevo integration only makes the contacts call. It does not make the transactional email call. There is no bug here. It is simply a feature that the built-in integration does not include.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why the Welcome Email Option Exists But Does Not Work
&lt;/h2&gt;

&lt;p&gt;CF7's Brevo settings panel includes a "Send a welcome email" checkbox. The existence of this option implies that checking it will send the welcome email. But based on Brevo's own confirmation that no email send request is reaching their servers, this option either does not trigger the transactional email API call or only works under specific conditions that are not documented.&lt;/p&gt;

&lt;p&gt;The most likely explanation is that this setting was intended to work alongside Brevo's double opt-in confirmation email feature, not with a custom transactional template. Brevo has a built-in confirmation email mechanism tied to lists, which is separate from sending a transactional template with a specific template ID. The checkbox in CF7 may only activate the Brevo list-level confirmation email, not a custom template send.&lt;/p&gt;

&lt;h2&gt;
  
  
  How to Actually Send a Welcome Email After CF7 Submission
&lt;/h2&gt;

&lt;p&gt;To send a welcome email using a specific Brevo transactional template after a CF7 submission, you need to make the second API call yourself. The cleanest way to do this is with a PHP function that hooks into CF7's submission event.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="nf"&gt;add_action&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'wpcf7_before_send_mail'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'send_brevo_welcome_email'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;send_brevo_welcome_email&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$contact_form&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="n"&gt;int&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="nv"&gt;$contact_form&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;id&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="no"&gt;YOUR_FORM_ID&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="nv"&gt;$submission&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;WPCF7_Submission&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;get_instance&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nv"&gt;$submission&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="nv"&gt;$data&lt;/span&gt;  &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$submission&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;get_posted_data&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="nv"&gt;$email&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;sanitize_email&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$data&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'your-email'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;??&lt;/span&gt; &lt;span class="s1"&gt;''&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nv"&gt;$name&lt;/span&gt;  &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;sanitize_text_field&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$data&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'your-name'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;??&lt;/span&gt; &lt;span class="s1"&gt;''&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;empty&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$email&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="nv"&gt;$api_key&lt;/span&gt;     &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;defined&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'BREVO_API_KEY'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;?&lt;/span&gt; &lt;span class="no"&gt;BREVO_API_KEY&lt;/span&gt; &lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;''&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nv"&gt;$template_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;12&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// replace with your Brevo template ID&lt;/span&gt;

    &lt;span class="nv"&gt;$response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;wp_remote_post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'https://api.brevo.com/v3/smtp/email'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
        &lt;span class="s1"&gt;'headers'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
            &lt;span class="s1"&gt;'api-key'&lt;/span&gt;      &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;$api_key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="s1"&gt;'Content-Type'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'application/json'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;],&lt;/span&gt;
        &lt;span class="s1"&gt;'body'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;wp_json_encode&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;
            &lt;span class="s1"&gt;'templateId'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;$template_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="s1"&gt;'to'&lt;/span&gt;         &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;[[&lt;/span&gt;&lt;span class="s1"&gt;'email'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;$email&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'name'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;$name&lt;/span&gt;&lt;span class="p"&gt;]],&lt;/span&gt;
            &lt;span class="s1"&gt;'params'&lt;/span&gt;     &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
                &lt;span class="s1"&gt;'FIRSTNAME'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;$name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="p"&gt;],&lt;/span&gt;
        &lt;span class="p"&gt;]),&lt;/span&gt;
        &lt;span class="s1"&gt;'timeout'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;15&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;]);&lt;/span&gt;

    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;is_wp_error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$response&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nb"&gt;error_log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'[CF7-&amp;gt;Brevo] Welcome email failed: '&lt;/span&gt; &lt;span class="mf"&gt;.&lt;/span&gt; &lt;span class="nv"&gt;$response&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;get_error_message&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Add your API key to &lt;code&gt;wp-config.php&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="nb"&gt;define&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'BREVO_API_KEY'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'your-brevo-api-key-here'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Your template ID is the number that appears in Brevo under Email Templates when you open the template you want to use.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;params&lt;/code&gt; object lets you pass personalisation variables into your Brevo template. If your template uses &lt;code&gt;{{ params.FIRSTNAME }}&lt;/code&gt;, the value you pass in &lt;code&gt;params.FIRSTNAME&lt;/code&gt; will be substituted in.&lt;/p&gt;

&lt;h2&gt;
  
  
  The No-Code Option
&lt;/h2&gt;

&lt;p&gt;If you do not want to manage PHP code, &lt;a href="https://www.contactformtoapi.com/brevo-integration-with-contact-form-to-any-api/" rel="noopener noreferrer"&gt;Contact Form to API&lt;/a&gt; lets you connect CF7 directly to Brevo's transactional email endpoint from the WordPress dashboard. You set the API endpoint, add your Brevo API key as the &lt;code&gt;api-key&lt;/code&gt; header, configure the JSON body with your template ID and the CF7 fields mapped to the &lt;code&gt;to&lt;/code&gt; and &lt;code&gt;params&lt;/code&gt; values, and the welcome email fires automatically on every form submission.&lt;/p&gt;

&lt;p&gt;This gives you the same result as the custom PHP approach without writing or maintaining any code. And because it calls the transactional email endpoint directly, you know the welcome email request is actually being sent to Brevo rather than silently not firing.&lt;/p&gt;

&lt;h2&gt;
  
  
  Quick Check: Is Your Template Actually Active?
&lt;/h2&gt;

&lt;p&gt;Before assuming the integration is the problem, confirm one thing in Brevo. Go to Email Templates and open the template you want to use. Check that the status shows as Active. Brevo will not send emails for draft or inactive templates even if the API call reaches them. The site owner in the forum confirmed their template was active, but this is the first thing to verify if you are debugging this issue yourself.&lt;/p&gt;

&lt;h2&gt;
  
  
  Summary
&lt;/h2&gt;

&lt;p&gt;CF7's built-in Brevo integration adds contacts successfully but does not make the separate API call needed to send a transactional welcome email. The "Send a welcome email" checkbox in CF7's settings does not trigger a transactional template send. To actually send a Brevo welcome email on CF7 form submission, you need either a custom PHP function that calls &lt;code&gt;POST /v3/smtp/email&lt;/code&gt; or a plugin like Contact Form to API that handles that call from a configuration interface.&lt;/p&gt;

</description>
      <category>wordpress</category>
      <category>emailmarketing</category>
      <category>api</category>
      <category>webdev</category>
    </item>
    <item>
      <title>CF7 to CleverReach Integration Errors: What Each One Means and How to Fix It</title>
      <dc:creator>Rahul Sharma</dc:creator>
      <pubDate>Fri, 19 Jun 2026 10:10:12 +0000</pubDate>
      <link>https://dev.to/rahul_sharma_15bd129bc69e/cf7-to-cleverreach-integration-errors-what-each-one-means-and-how-to-fix-it-2ff8</link>
      <guid>https://dev.to/rahul_sharma_15bd129bc69e/cf7-to-cleverreach-integration-errors-what-each-one-means-and-how-to-fix-it-2ff8</guid>
      <description>&lt;p&gt;A site owner posted on the WordPress forums that their CF7 to CleverReach integration had been throwing errors for a week. The logs looked scary. The plugin author said the spam-related errors could be ignored. Thread closed.&lt;/p&gt;

&lt;p&gt;That answer is only partly right. The logs in this thread actually contain three completely different types of errors, each with a different cause and a different fix. If you are seeing similar errors in your CF7 to CleverReach setup, here is what each one actually means.&lt;/p&gt;

&lt;h2&gt;
  
  
  Error 1: &lt;code&gt;invalid_grant&lt;/code&gt; on the OAuth Token Endpoint
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight http"&gt;&lt;code&gt;&lt;span class="err"&gt;POST https://rest.cleverreach.com/oauth/token.php
400 Bad Request
{"error":"invalid_grant","error_description":"Authorization code doesn't exist or is invalid for the client"}
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is the most important error in the log and the only one that is genuinely a problem with your integration. It means the OAuth authorization code your plugin used to connect to CleverReach has expired or been invalidated.&lt;/p&gt;

&lt;p&gt;When you first connect the CF7 CleverReach plugin to your account, CleverReach issues an authorization code. That code is exchanged for an access token and a refresh token. If something invalidates those tokens, the plugin can no longer authenticate and every attempt to refresh fails with &lt;code&gt;invalid_grant&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;This happens when you change your CleverReach password, when you revoke the app's access in your CleverReach account settings, or when the authorization code expires before the plugin could exchange it. It can also happen if you migrated your WordPress site to a new server and the stored tokens did not transfer correctly.&lt;/p&gt;

&lt;p&gt;The fix is to disconnect the plugin from CleverReach and reconnect it from scratch. Go to the plugin settings, revoke the current connection, and go through the authorization flow again. This gives you a fresh set of tokens and clears the &lt;code&gt;invalid_grant&lt;/code&gt; error.&lt;/p&gt;

&lt;h2&gt;
  
  
  Error 2: &lt;code&gt;403 Forbidden: email not allowed&lt;/code&gt;
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight http"&gt;&lt;code&gt;&lt;span class="err"&gt;POST https://rest.cleverreach.com/v2/forms.json/239438/send/activate
403 Forbidden
{"error":{"code":403,"message":"Forbidden: email not allowed"}}
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This error appears dozens of times in the log. The plugin author said to ignore it because CleverReach rejects known spam emails. That is accurate but there is more to understand here.&lt;/p&gt;

&lt;p&gt;CleverReach maintains a blocklist of email addresses and domains that have been flagged as spam sources, disposable inboxes, or previously bounced addresses. When your CF7 form receives a submission from one of these addresses, CleverReach refuses to add it to your list. That is actually CleverReach protecting your sender reputation, not something going wrong with your integration.&lt;/p&gt;

&lt;p&gt;Looking at the actual email addresses in the log, some are clearly spam bot submissions. The field values for several of these entries contain Telegram phishing links and promotional spam text in what should be a name field. These are not real people filling in your form. They are bots.&lt;/p&gt;

&lt;p&gt;So the &lt;code&gt;403 email not allowed&lt;/code&gt; errors are doing you a favour. CleverReach is rejecting spam bot submissions that should never have reached your email list in the first place. You do not need to fix this error. You need to stop the spam bots from submitting your form so they never hit the CleverReach API at all.&lt;/p&gt;

&lt;p&gt;Adding a honeypot field or enabling Cloudflare Turnstile on your CF7 form stops most bots before they submit. CF7 has a built-in honeypot option that bots fill in but real users cannot see, and CF7 rejects any submission where that field has a value.&lt;/p&gt;

&lt;h2&gt;
  
  
  Error 3: &lt;code&gt;400 Bad Request: duplicate address&lt;/code&gt;
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight http"&gt;&lt;code&gt;&lt;span class="err"&gt;POST https://rest.cleverreach.com/v2/groups.json/1173358/receivers
400 Bad Request
{"error":{"code":400,"message":"Bad Request: \"duplicate address 'ackstein01@aol.com'\""}}
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This error means someone tried to sign up with an email address that already exists in your CleverReach group. This is not a bug and it is not something you need to fix. CleverReach prevents duplicate email addresses in the same group.&lt;/p&gt;

&lt;p&gt;If you want to update an existing contact's details when they submit your form again, you need to use CleverReach's update endpoint rather than the add endpoint. Most CF7 integration plugins use the add endpoint by default and do not handle duplicates.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Real Problem: Your Form Has No Spam Protection
&lt;/h2&gt;

&lt;p&gt;The most revealing thing in this log is what the spam bot submissions look like. Fields that should contain a person's name contain Telegram links and cryptocurrency scam text. This means your CF7 form is completely unprotected and bots are submitting it freely.&lt;/p&gt;

&lt;p&gt;Every spam submission that reaches CleverReach uses one of your API calls and potentially triggers a 403 error in your logs. More importantly, if a bot ever submits with a real-looking email address that CleverReach does not recognise as spam, that address gets added to your marketing list without that person's consent.&lt;/p&gt;

&lt;p&gt;Fixing your spam problem protects your CleverReach list quality, reduces unnecessary API calls, and clears most of the errors from your log. The best options are CF7's built-in honeypot, Google reCAPTCHA, or Cloudflare Turnstile, all of which CF7 supports natively.&lt;/p&gt;

&lt;h2&gt;
  
  
  A More Reliable Way to Connect CF7 to CleverReach
&lt;/h2&gt;

&lt;p&gt;The dedicated CF7 CleverReach plugin works for many people but it has limitations. It relies on an OAuth connection that can break when credentials change, it does not handle duplicate contacts gracefully, and it gives you limited control over which fields are sent and how.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.contactformtoapi.com/contact-form-7-email-automation/" rel="noopener noreferrer"&gt;Contact Form to API&lt;/a&gt; connects CF7 directly to CleverReach's REST API using a straightforward API key instead of OAuth. API keys do not expire the way OAuth tokens do, so you do not get &lt;code&gt;invalid_grant&lt;/code&gt; errors weeks after setting up the integration. You also get full control over the field mapping, which means you can send exactly the data CleverReach expects and avoid payload issues.&lt;/p&gt;

&lt;h2&gt;
  
  
  Quick Summary
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Error&lt;/th&gt;
&lt;th&gt;What it means&lt;/th&gt;
&lt;th&gt;What to do&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;invalid_grant&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;OAuth tokens expired or revoked&lt;/td&gt;
&lt;td&gt;Disconnect and reconnect the plugin in settings&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;403 email not allowed&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;CleverReach blocked a spam or invalid email&lt;/td&gt;
&lt;td&gt;Add spam protection to your CF7 form&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;400 duplicate address&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Email already exists in CleverReach group&lt;/td&gt;
&lt;td&gt;Normal behaviour, or switch to an update endpoint&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

</description>
      <category>wordpress</category>
      <category>emailmarketing</category>
      <category>api</category>
      <category>webdev</category>
    </item>
    <item>
      <title>CF7 Webhook Getting a 400 Bad Request? The Problem Is Probably Not JSON Encoding</title>
      <dc:creator>Rahul Sharma</dc:creator>
      <pubDate>Thu, 18 Jun 2026 12:30:47 +0000</pubDate>
      <link>https://dev.to/rahul_sharma_15bd129bc69e/cf7-webhook-getting-a-400-bad-request-the-problem-is-probably-not-json-encoding-37hi</link>
      <guid>https://dev.to/rahul_sharma_15bd129bc69e/cf7-webhook-getting-a-400-bad-request-the-problem-is-probably-not-json-encoding-37hi</guid>
      <description>&lt;p&gt;A developer posted on the WordPress forums with a detailed bug report. Their CF7 webhook was sending data to an external REST API and getting a 400 Bad Request response. The API was returning validation errors saying required fields were empty. They had checked the debug log, seen the request body wrapped in quotes, and concluded the plugin was double-encoding the JSON.&lt;/p&gt;

&lt;p&gt;The plugin author explained that the debug log shows a string representation of the payload for logging purposes, not the actual bytes sent over the network. The JSON was never double-encoded.&lt;/p&gt;

&lt;p&gt;The real problem was a wrong field name. The developer used &lt;code&gt;_notify_leadtype_id&lt;/code&gt; but the API required &lt;code&gt;leadtype_id&lt;/code&gt;. Because the field name was wrong, the API never received a value for that required field and rejected the whole request.&lt;/p&gt;

&lt;p&gt;This is one of the most common reasons CF7 webhooks return 400 errors, and it is almost always misdiagnosed as an encoding issue. This post explains how to read webhook debug logs correctly and how to find the actual cause of 400 errors.&lt;/p&gt;

&lt;h2&gt;
  
  
  What the Debug Log Is Actually Showing You
&lt;/h2&gt;

&lt;p&gt;When a CF7 webhook plugin sends a debug email after a failed request, the request body section looks something like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="err"&gt;Request&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;Body:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="s2"&gt;"{&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;  &lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;lead_type_id&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;: 318825,&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;  &lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;contact_name&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;: &lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;John Doe&lt;/span&gt;&lt;span class="se"&gt;\"\n&lt;/span&gt;&lt;span class="s2"&gt;}"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The outer quotes and all those backslash characters make it look like the JSON has been double-encoded or stringified inside a string. It has not. This is just how a JSON string looks when it is printed as a PHP string for logging purposes. The backslashes are escape characters that PHP adds when displaying a string that contains quote marks.&lt;/p&gt;

&lt;p&gt;The actual bytes sent to the API over the network look like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"lead_type_id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;318825&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"contact_name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"John Doe"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That is valid JSON. The API receives it correctly formatted. The debug log display is misleading but it is not evidence of a problem with the encoding.&lt;/p&gt;

&lt;p&gt;If you see a 400 response with validation errors saying fields are empty, the encoding is almost certainly fine. Look at the field names instead.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Actual Cause of "Field Cannot Be Empty" on a 400 Response
&lt;/h2&gt;

&lt;p&gt;When an API returns a 400 with a message like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"code"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;400&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"success"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"message"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"validation"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"leadtype_id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"Field cannot be empty"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It means the API received the request, parsed the JSON successfully, and then checked whether required fields were present. It found that &lt;code&gt;leadtype_id&lt;/code&gt; was missing or null.&lt;/p&gt;

&lt;p&gt;This happens for three reasons in CF7 webhook setups:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Wrong field name in your webhook body.&lt;/strong&gt; You used &lt;code&gt;_notify_leadtype_id&lt;/code&gt; but the API expects &lt;code&gt;leadtype_id&lt;/code&gt;. The API ignores the field it does not recognise and treats the required field as empty. This was exactly the problem in the forum thread.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Field value is empty because the CF7 field was not filled in.&lt;/strong&gt; If you are mapping a CF7 form field to the API and the user left that field blank, the API receives an empty string or null for a required field.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The value is a hardcoded static value that was not included correctly.&lt;/strong&gt; The developer needed to send &lt;code&gt;lead_type_id: 318825&lt;/code&gt; as a fixed integer, not from a form field. If the webhook plugin does not support hardcoded static values in the request body, the field either gets sent as the placeholder text or not at all.&lt;/p&gt;

&lt;h2&gt;
  
  
  How to Find the Correct Field Names
&lt;/h2&gt;

&lt;p&gt;Every API has documentation that lists the exact field names it expects. These are case-sensitive and exact. &lt;code&gt;leadtype_id&lt;/code&gt;, &lt;code&gt;lead_type_id&lt;/code&gt;, and &lt;code&gt;LeadTypeId&lt;/code&gt; are all different field names that an API treats as completely different keys.&lt;/p&gt;

&lt;p&gt;Before setting up a webhook, open the API documentation for the endpoint you are calling. Find the request body schema and write down the exact field names as they appear in the docs. Do not copy them from a code example or a forum post. Copy them from the official reference page for that specific endpoint.&lt;/p&gt;

&lt;p&gt;For the RO App API in this thread, the correct documentation page was &lt;code&gt;roapp.readme.io/reference/create-lead&lt;/code&gt;. The field there was &lt;code&gt;leadtype_id&lt;/code&gt;, not &lt;code&gt;_notify_leadtype_id&lt;/code&gt;. One character difference, completely silent failure.&lt;/p&gt;

&lt;h2&gt;
  
  
  Testing Your Field Names Before Connecting CF7
&lt;/h2&gt;

&lt;p&gt;Before wiring up your CF7 form, test the API call directly with your exact field names and values. The fastest way is cURL from a terminal:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-X&lt;/span&gt; POST https://api.example.com/lead/ &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"Authorization: Bearer YOUR_API_TOKEN"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"Content-Type: application/json"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="s1"&gt;'{
    "leadtype_id": 318825,
    "contact_name": "Test User",
    "contact_phone": "123456789"
  }'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If this returns a success response, your field names are correct and your credentials work. Then when you set up CF7, you know any problems are in the mapping, not in your understanding of the API.&lt;/p&gt;

&lt;p&gt;If this returns a 400, the problem is in your request. Check the field names against the documentation one more time.&lt;/p&gt;

&lt;h2&gt;
  
  
  Sending Static Values Alongside Form Fields
&lt;/h2&gt;

&lt;p&gt;The developer needed to send &lt;code&gt;leadtype_id&lt;/code&gt; as a fixed number (not from a form field) alongside dynamic values like &lt;code&gt;contact_name&lt;/code&gt; from the form. This is a common requirement when integrating with CRMs that need an internal category ID alongside the contact details.&lt;/p&gt;

&lt;p&gt;Not all CF7 webhook plugins support mixing static values and dynamic form field values in the same request body. Some only support CF7 field placeholders.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.contactformtoapi.com/contact-form-7-json-mapping/" rel="noopener noreferrer"&gt;Contact Form to API&lt;/a&gt; supports both. You can set some fields as static hardcoded values (like &lt;code&gt;"leadtype_id": 318825&lt;/code&gt;) and map other fields dynamically from your CF7 form submission. The request body is built correctly and sent as a real JSON object to your API endpoint.&lt;/p&gt;

&lt;p&gt;This is exactly the use case from the forum thread: a fixed lead type ID that never changes, combined with dynamic contact details from the form.&lt;/p&gt;

&lt;h2&gt;
  
  
  Quick Checklist When You Get a 400 Response
&lt;/h2&gt;

&lt;p&gt;Work through these before assuming anything is wrong with the plugin:&lt;/p&gt;

&lt;p&gt;First, check the field names in your webhook body against the API documentation. Copy and paste them directly from the official reference. Do not type them from memory.&lt;/p&gt;

&lt;p&gt;Second, look at the validation errors in the response body. If the API tells you which field is empty or invalid, it is telling you exactly what to fix.&lt;/p&gt;

&lt;p&gt;Third, test the API call directly with cURL or Postman using your exact payload. If it works there, the problem is in how your plugin is building the request.&lt;/p&gt;

&lt;p&gt;Fourth, check whether your plugin supports static hardcoded values in the request body. If you need to send a fixed ID alongside form fields and the plugin does not support that, you will always get a missing field error.&lt;/p&gt;

</description>
      <category>wordpress</category>
      <category>api</category>
      <category>webdev</category>
      <category>tutorial</category>
    </item>
  </channel>
</rss>
