This guide explains how to connect a WordPress Elementor Pro form to Intercom using a Next.js API route as the middleman.
- WordPress Website with Elementor Pro installed.
- Plugin: Telephone field for Elementor Forms (Free) - Required for valid phone formats.
- Next.js Application hosting the API route.
- Intercom Account with access to Developer settings.
Ensure your app/api/leads/route.ts file is set up with the following code. This code is specifically designed to handle the Advanced Data structure sent by Elementor and the strict requirements of Intercom.
import { NextResponse } from "next/server";
import { Client } from "intercom-client";
const intercom = new Client({
tokenAuth: {
token: process.env.INTERCOM_ACCESS_TOKEN as string,
},
});
export async function POST(request: Request) {
try {
const contentType = request.headers.get("content-type") || "";
let data: Record<string, any> = {};
// 1. Robust Data Parsing
if (contentType.includes("application/json")) {
try {
data = await request.json();
} catch (e) {
console.error("JSON parsing failed", e);
return NextResponse.json(
{ error: "Invalid JSON body" },
{ status: 400 }
);
}
} else {
const formData = await request.formData();
formData.forEach((value, key) => {
data[key] = value.toString();
});
}
// 2. Initialize Variables
// Elementor Advanced Webhooks send data as keys like 'fields[email][value]'
const email = data["fields[email][value]"];
const name = data["fields[name][value]"];
const phone = data["fields[phone][value]"];
const jobTitle = data["fields[jobtitle][value]"];
// 3. Validation
if (!email) {
return NextResponse.json(
{ error: "Email is required." },
{ status: 400 }
);
}
// 4. Send to Intercom
// Only sending the requested fields
const contact = await intercom.contacts.createLead({
email: email,
name: name,
// Make sure you're using Telephone field for Elementor Forms plugin
phone: phone,
customAttributes: {
// See Step 3 to add these custom attributes
work_role: jobTitle, // Explicitly mapping jobtitle to custom attributes
source: "Add a name or URL referrer",
},
signedUpAt: Math.floor(Date.now() / 1000),
});
return NextResponse.json({
success: true,
message: "Lead captured successfully",
intercom_id: contact.id,
});
} catch (error: any) {
console.error("Intercom Integration Error:", error);
return NextResponse.json(
{ error: "Failed to process submission", details: error.message },
{ status: 500 }
);
}
}Environment Variables: Create or update your .env.local file with your Intercom Access Token:
INTERCOM_ACCESS_TOKEN=your_token_here
To get this token:
- Go to Intercom Developer Hub.
- Create a new App (or select an existing one).
- Navigate to Configure > Authentication.
- Copy the Access Token.
If your Next.js project uses middleware.ts (common for authentication like Clerk, NextAuth, or Supabase), you must exclude the API route from protection. If you don't, Elementor's request will be blocked (401 Unauthorized) or redirected (307), causing the webhook to fail.
Example (Generic Middleware matcher): Ensure your matcher excludes /api/leads:
export const config = {
matcher: [
/* ... existing matcher ... */
'/((?!api/leads|_next/static|_next/image|favicon.ico).*)',
],
}Example (Clerk Auth): If using Clerk, add it to publicRoutes or ignoredRoutes:
export default authMiddleware({
publicRoutes: ["/api/leads"],
});Go to your WordPress Admin dashboard and edit the page containing your form with Elementor.
Intercom is very strict about phone numbers. They must include the + prefix and country code (e.g., +5511999999999). The default Elementor "Tel" field does not enforce this, often causing the integration to fail with generic errors.
The Fix:
- Install and activate the free plugin: Telephone field for Elementor Forms.
- Edit your Elementor Form.
- Delete your existing standard "Tel" field.
- Add the new "Telephone" field (added by the plugin) to your form.
- This field adds a country picker and ensures the submitted value is always formatted correctly (e.g.,
+1 555...or+55 11...).
The API route expects specific Field IDs to map the data correctly. Click on each form field and set the ID (under the Advanced tab) to match exactly:
- Name Field: Set ID to name
- Email Field: Set ID to email
- Telephone Field (New): Set ID to phone
- Job/Role Field: Set ID to jobtitle
- Click on the Form widget.
- Go to Content > Actions After Submit.
- Add Webhook to the list of actions.
A new "Webhook" tab will appear in the Elementor sidebar.
- Webhook URL: Enter the full URL to your Next.js API route.
- Dev:
https://your-tunnel-url.ngrok.io/api/leads - Prod:
https://your-domain.com/api/leads
- Advanced Data: Toggle this to YES.
- Crucial: The API code relies on the structure provided by "Advanced Data" (e.g.,
fields[email][value]). If this is off, the integration will fail.
The code sends work_role and source as Custom Attributes. You must define these in Intercom for them to appear cleanly in the UI (though Intercom often creates them automatically on the first sync).
- Go to Intercom Settings > Data > People Data.
- Create a new attribute called
work_role(Text) and `source (Text) if they don't exist.
- Open your WordPress page (ensure it's not the editor preview, use the live page or preview link).
- Fill out the form with test data. Use the country picker to select your country and type a number.
- Submit.
- Check Intercom: Go to "Contacts" and look for the new lead. You should see the Name, Email, properly formatted Phone, and the Custom Attributes (Job Title/Role) populated.
- Error 500 / "Failed to process": Check the Next.js console logs.
- "Email is required": This usually means the Elementor "Advanced Data" switch is OFF, or the Field ID for email is not set to email in Elementor.
- Intercom API Error (Phone): If you still see errors regarding phone numbers, ensure the "Telephone" plugin is active and the user is actually using the country picker. The plugin should force the + prefix automatically.
- Webhook Failures (401/307): Verify your Middleware settings (Step 1). The webhook route MUST be public.