If you are building a headless WordPress site with React or Next.js and calling CF7's REST API, you have probably hit this:
{
"code": "wpcf7_forbidden",
"message": "You are not allowed to access the requested contact form.",
"data": { "status": 403 }
}
The confusing part: you are logged in as an admin. You can see the form in the WordPress backend. But the API tells you that you are forbidden.
This happens because wpcf7_forbidden covers two completely different situations that need completely different fixes. Getting them mixed up is why most debugging attempts fail.
The Two Endpoints Are Not the Same
CF7 has two separate REST API endpoints and they have different authentication requirements:
GET endpoint — reads form configuration:
GET /wp-json/contact-form-7/v1/contact-forms/{id}
This returns the form's HTML, fields, and schema. It requires the user to have the wpcf7_edit_contact_form capability, which means they must be logged in as an administrator or editor. Unauthenticated requests always get wpcf7_forbidden.
POST endpoint — submits the form:
POST /wp-json/contact-form-7/v1/contact-forms/{id}/feedback
This processes a form submission. It does not require authentication. Any visitor can submit a form. But it does require specific hidden fields in the payload.
Most wpcf7_forbidden errors on the feedback endpoint are not authentication problems. They are missing field problems.
Cause 1: Calling the GET Endpoint Without Authentication
A developer posted on the WordPress forums that their headless React app was getting wpcf7_forbidden when fetching form data. They were calling:
GET /wp-json/contact-form-7/v1/contact-forms/21798
The CF7 source code for this endpoint's permission callback is:
'permission_callback' => static function (WP_REST_Request $request) {
$id = (int) $request->get_param('id');
if (current_user_can('wpcf7_edit_contact_form', $id)) {
return true;
} else {
return new WP_Error(
'wpcf7_forbidden',
__('You are not allowed to access the requested contact form.', 'contact-form-7'),
array('status' => 403)
);
}
},
This capability check (wpcf7_edit_contact_form) returns false for unauthenticated requests. There is no way around this without authentication.
The fix: Do not call the GET endpoint from client-side code without authentication. Instead, fetch the form HTML server-side in Next.js using your WordPress credentials, then render it client-side. Or hardcode the form HTML from WordPress admin and skip the GET endpoint entirely.
In Next.js, fetch the form server-side:
// In a Next.js Server Component or getServerSideProps
const response = await fetch(
`${process.env.WP_URL}/wp-json/contact-form-7/v1/contact-forms/${formId}`,
{
headers: {
Authorization: 'Basic ' + Buffer.from(
`${process.env.WP_USER}:${process.env.WP_APP_PASSWORD}`
).toString('base64'),
},
}
);
const formData = await response.json();
Use WordPress Application Passwords (generated under Users, Profile, Application Passwords) rather than your account password.
Cause 2: Missing Required Hidden Fields on the Feedback Endpoint
When the wpcf7_forbidden error appears on the POST feedback endpoint during form submission (not the GET endpoint), the cause is almost always missing hidden fields in the payload.
CF7's feedback endpoint requires these fields alongside your form data:
| Field | Value | Where to get it |
|---|---|---|
_wpcf7 |
Form ID (integer) | From the form's edit URL in WordPress admin |
_wpcf7_version |
CF7 version | From the rendered form HTML |
_wpcf7_locale |
Site locale | Usually en_US
|
_wpcf7_unit_tag |
Unique form tag | From the rendered form HTML |
_wpcf7_container_post |
Post ID where form is embedded |
0 if not embedded in a post |
A minimal correct payload:
const formData = new FormData();
formData.append('_wpcf7', '123'); // your form ID
formData.append('_wpcf7_version', '5.9.8'); // CF7 version
formData.append('_wpcf7_locale', 'en_US');
formData.append('_wpcf7_unit_tag', 'wpcf7-f123-p456-o1');
formData.append('_wpcf7_container_post', '0');
formData.append('your-name', 'Jane Smith');
formData.append('your-email', 'jane@example.com');
formData.append('your-message', 'Hello');
const response = await fetch(
`https://your-wp-site.com/wp-json/contact-form-7/v1/contact-forms/123/feedback`,
{
method: 'POST',
body: formData,
}
);
The _wpcf7_unit_tag value is generated by CF7 when it renders the form. In a headless setup, you can either fetch it from the GET endpoint server-side or construct it using the pattern wpcf7-f{formId}-p{postId}-o{instance}.
Cause 3: Nginx Redirect Misconfiguration
One developer reported wpcf7_forbidden appearing on all their forms suddenly with no other changes. After debugging, they found it was caused by a redirect rule in their Nginx configuration that was rewriting the request URL before it reached WordPress.
The Nginx rule was modifying the path in a way that stripped or altered the form ID segment of the URL. WordPress received a modified URL that did not match the route pattern, fired the permission callback with incorrect parameters, and returned wpcf7_forbidden.
If your wpcf7_forbidden error appeared suddenly without any code changes, check your Nginx configuration for any recently added location blocks or rewrite rules that might affect /wp-json/ paths.
The Headless CF7 Integration Pattern That Avoids All of This
If you are building a headless WordPress site and your goal is to collect form data and send it somewhere useful (a CRM, an email service, a Slack channel), there is a simpler architecture that avoids the REST API authentication complexity entirely.
Keep CF7 on the WordPress side. Use Contact Form to API to forward form submissions to your external service via the server-side wpcf7_before_send_mail hook. Your Next.js or React frontend embeds the WordPress CF7 form directly (via an iframe or a fetched HTML block), the form submits to WordPress normally, and WordPress forwards the data to your CRM.
No GET endpoint authentication. No hidden field management. No CORS configuration. The form submission stays within the WordPress ecosystem and the outbound CRM call happens server-to-server.
Quick Reference
| Scenario | Error cause | Fix |
|---|---|---|
| Calling GET endpoint without auth |
wpcf7_edit_contact_form capability check fails |
Use Application Password auth server-side |
| POST feedback with missing fields | Required hidden fields absent | Include all _wpcf7_* hidden fields |
| Sudden 403 on all forms, no code change | Nginx redirect modifying URL | Check Nginx config for recently added rewrite rules |
| Intermittent 403 on form submission | Nonce expired between page load and submit | CF7 handles nonces internally — usually not the cause on standard submissions |
Top comments (0)