Webhook Integration Guide
Connect your WordPress forms to n8n, Make, Zapier, and custom APIs to power your AI agents.
What is a Webhook?
Simple Explanation
Think of a webhook as a phone number for your software. When someone submits a form on your WordPress site, it "calls" this number and sends the form data. The receiver (n8n, Make, Zapier) processes the data and sends back a response.
User Submits Form
↓
WordPress packages data
↓
Sends POST request to webhook URL
↓
Webhook processes data (AI, database, email, etc.)
↓
Sends response back to WordPress
↓
User sees result
Technical Explanation
A webhook is an HTTP endpoint that:
- Receives POST requests with JSON data
- Processes the data (triggers AI, saves to database, sends emails, etc.)
- Returns a JSON response
- Operates asynchronously
Request Format
POST https://your-webhook-url.com/endpoint
Content-Type: application/json
{
"agent_id": 123,
"agent_name": "AI Image Generator",
"form_data": {
"field_description": "A futuristic city",
"field_style": "Digital Art"
},
"timestamp": "2025-01-09 10:30:00",
"user_id": 5
}
n8n Integration (Recommended)
Why n8n?
- ✅ Open source - Free forever
- ✅ Self-hosted - Full control over data
- ✅ Visual editor - No coding required
- ✅ 400+ integrations - Connect to anything
- ✅ AI-friendly - Built-in OpenAI, Anthropic nodes
Setup n8n
Option 1: n8n Cloud (Easiest)
- Go to n8n.cloud
- Create free account
- Create new workflow
- Done! Your instance is ready
Option 2: Self-Hosted (Most Control)
# Using Docker
docker run -it --rm \
--name n8n \
-p 5678:5678 \
-v ~/.n8n:/home/node/.n8n \
n8nio/n8n
# Access at: http://localhost:5678
Create Your First Workflow
Step 1: Add Webhook Node
- Click "+" to add node
- Search for "Webhook"
- Select "Webhook" trigger
- Configure:
- HTTP Method: POST
- Path:
/ai-agent - Response Mode: "Wait for Response"
- Copy the Production URL
Example URL: https://your-n8n.app.n8n.cloud/webhook/ai-agent
Step 2: Process the Data
Example: OpenAI Image Generation
- Add OpenAI node
- Connect it to Webhook
- Configure:
- Resource: Image
- Operation: Generate
- Prompt:
{{ $json.body.form_data.field_description }} - Size:
{{ $json.body.form_data.field_size }}
Step 3: Format Response
Add Code node:
// Extract the generated image URL
const imageUrl = $json.data[0].url;
// Format response for WordPress
return {
output_html: `
<div class="ai-result">
<h3>✨ Your AI Image is Ready!</h3>
<img src="${imageUrl}" alt="Generated Image"
style="max-width: 100%; border-radius: 10px;">
<p><a href="${imageUrl}" download>Download Full Resolution</a></p>
</div>
`,
image_url: imageUrl,
success: true
};
Step 4: Return Response
- Add Respond to Webhook node
- Connect to Code node
- Configure:
- Response Code: 200
- Response Body:
{{ $json }}
Step 5: Activate
- Click "Active" toggle in top-right
- Your workflow is live!
Complete n8n Workflows
Workflow 1: AI Chatbot
[Webhook Trigger]
↓
[Code: Extract message]
↓
[OpenAI: Chat completion]
↓
[Code: Format HTML response]
↓
[Respond to Webhook]
Response Formatting:
const reply = $json.choices[0].message.content;
return {
output_html: `
<div class="chat-response">
<div class="ai-avatar">🤖</div>
<div class="ai-message">${reply}</div>
</div>
`
};
Workflow 2: Document Analysis
[Webhook Trigger]
↓
[Code: Decode base64 file]
↓
[HTTP Request: Upload to storage]
↓
[OpenAI: Analyze document]
↓
[Send Email: Results]
↓
[Respond to Webhook]
Workflow 3: Lead to CRM
[Webhook Trigger]
↓
[Code: Format lead data]
↓
[HubSpot: Create contact]
↓
[Slack: Notify team]
↓
[Send Email: Confirmation]
↓
[Respond to Webhook]
Make (Integromat) Integration
Setup in Make
Step 1: Create Scenario
- Log into make.com
- Click "Create a new scenario"
- Search for "Webhooks"
- Select "Custom Webhook"
Step 2: Create Webhook
- Click "Add" next to webhook
- Name it: "WordPress AI Agent"
- Click "Save"
- Copy the webhook URL
Example: https://hook.us1.make.com/abc123xyz
Step 3: Add Modules
Example: OpenAI Integration
- Add OpenAI module
- Select "Create an Image"
- Map fields:
- Prompt:
{{body.form_data.field_description}} - Size:
1024x1024
- Prompt:
- Add Webhook Response module
- Map response
Step 4: Activate
- Toggle "Scheduling" to ON
- Set to "Immediately"
- Click "Save"
Make Examples
- Email Automation:
Webhook → Gmail (Send Email) → Webhook Response - Database Storage:
Webhook → Google Sheets (Add Row) → Webhook Response - Multi-Service: Webhook branches to Airtable, Slack, and Email, then responds
Zapier Integration
Setup in Zapier
Step 1: Create Zap
- Log into zapier.com
- Click "Create Zap"
- Search for "Webhooks by Zapier"
- Select "Catch Hook" trigger
Step 2: Get Webhook URL
- Copy the custom webhook URL
- It looks like:
https://hooks.zapier.com/hooks/catch/123456/abc123/
Step 3: Test Webhook
- In WordPress, submit a test form
- Return to Zapier
- Click "Test trigger"
- You should see your form data
Step 4: Add Actions
Example: Send to Google Sheets
- Click "+" to add action
- Search "Google Sheets"
- Select "Create Spreadsheet Row"
- Map columns using Zapier variables
Returning Data to WordPress
Option A: Synchronous Response
Return finished HTML in the same webhook request. WordPress receives output_html and renders it immediately.
Required fields:
success- true or falsedisplay_preview- short summaryoutput_html- the exact markup you want
{
"success": true,
"display_preview": "Upload received from user@example.com",
"output_html": "<div class='ai-result-card'>...</div>"
}
Option B: Deferred Callback
Zapier retired the old Webhook Response action, so the plugin now ships with its own authenticated callback endpoint. WordPress shows "Processing..." until the callback arrives.
- Trigger: Webhooks by Zapier → Catch Hook
- Immediate reply: Return
{ "success": true }quickly - Processing steps: Run your AI calls, lookups, etc.
- Final action: POST back to the callback URL:
- URL: map
{{callback_url}} - Payload Type: json
- Data: Include
success,message,display_preview,output_html
- URL: map
- Built-in Webhook Response is missing → use callback POST
- Requests longer than ~25 seconds will time out
- Payloads larger than 10 MB are rejected
- For real-time output, n8n or Make are better
Custom API Integration
Building Your Own Endpoint
Example in PHP
<?php
// webhook-endpoint.php
header('Content-Type: application/json');
// Get POST data
$json = file_get_contents('php://input');
$data = json_decode($json, true);
// Extract form data
$description = $data['form_data']['field_description'] ?? '';
$email = $data['form_data']['field_email'] ?? '';
// Process with your AI service
$result = callYourAIService($description);
// Return response
echo json_encode([
'success' => true,
'output_html' => '<div>' . htmlspecialchars($result) . '</div>',
'processed_at' => date('Y-m-d H:i:s')
]);
?>
Example in Node.js
const express = require('express');
const app = express();
app.use(express.json());
app.post('/webhook', async (req, res) => {
const { form_data } = req.body;
// Process with AI
const result = await processWithAI(form_data.field_description);
// Return response
res.json({
success: true,
output_html: `<div class="result">${result}</div>`
});
});
app.listen(3000, () => console.log('Webhook listening on port 3000'));
Example in Python (Flask)
from flask import Flask, request, jsonify
import openai
app = Flask(__name__)
@app.route('/webhook', methods=['POST'])
def webhook():
data = request.json
form_data = data.get('form_data', {})
# Process with AI
result = process_with_ai(form_data.get('field_description', ''))
# Return response
return jsonify({
'success': True,
'output_html': f'<div class="result">{result}</div>'
})
if __name__ == '__main__':
app.run(port=5000)
Understanding Webhook Data
Data Structure Sent to Webhook
{
"agent_id": 123,
"agent_name": "AI Image Generator",
"form_data": {
"field_description": "User's input here",
"field_email": "user@example.com",
"field_quantity": "3",
"field_options": ["option1", "option2"],
"field_image_file_data": "base64_encoded_string...",
"field_image_file_name": "photo.jpg",
"field_image_file_type": "image/jpeg",
"field_image_file_size": 245678
},
"timestamp": "2025-01-09 10:30:45",
"user_id": 5,
"user_email": "logged-in-user@example.com"
}
Accessing Data in n8n
Text Field:
{{ $json.body.form_data.field_description }}
Select/Radio:
{{ $json.body.form_data.field_style }}
Multiple Values (checkboxes):
// In Code node
const options = $json.body.form_data.field_options;
// Returns: ["option1", "option2", "option3"]
File Upload:
// Base64 data
const fileData = $json.body.form_data.field_image_file_data;
const fileName = $json.body.form_data.field_image_file_name;
const fileType = $json.body.form_data.field_image_file_type;
// Convert to buffer if needed
const buffer = Buffer.from(fileData, 'base64');
Metadata:
Agent ID: {{ $json.body.agent_id }}
Agent Name: {{ $json.body.agent_name }}
Timestamp: {{ $json.body.timestamp }}
User ID: {{ $json.body.user_id }}
Understanding Webhook Responses (CRITICAL)
⚠️ THIS IS THE #1 SOURCE OF ISSUES! Your webhook MUST return the correct response format or nothing will display to users. Read this section carefully.
What WordPress Expects
When a user submits a form, WordPress sends data to your webhook and waits for a response. You MUST return:
- ✅ Valid JSON (not HTML, not plain text)
- ✅ HTTP Status 200 (success)
- ✅ Content-Type: application/json header
- ✅ A field containing HTML to display (
output_html,output, ormessage)
The MUST-HAVE Response Structure
✅ Minimum Required (Recommended Format)
{
"output_html": "<div>Your HTML content here</div>",
"success": true
}
Explanation:
output_html- REQUIRED. The HTML that will be displayed to the usersuccess- Recommended. Set totruefor successful responses
Complete Response Structure (All Fields)
{
"success": true,
"output_html": "<div class='ai-result'>Your response here</div>",
"display_preview": "Short summary for admin dashboard",
"message": "Optional success message",
"data": {
"any_additional": "data you want to include"
}
}
Field Descriptions:
success- Boolean (true/false)output_html- String containing HTML markupdisplay_preview- Optional short text for admin logsmessage- Optional status messagedata- Optional additional data (not displayed to user)
Accepted Response Formats (5 Options)
WordPress is flexible and accepts multiple formats. Here are ALL the formats that work:
Format 1: Standard (Recommended) ⭐
{
"output_html": "<div class='result'>Your content here</div>",
"success": true
}
When to use: Always. This is the clearest, most explicit format.
Format 2: Legacy Format
{
"output": "<h3>Result</h3><p>Content here</p>",
"success": true
}
When to use: If you're using older workflows. Uses output instead of output_html.
Format 3: OpenAI Message Format
{
"message": {
"content": "Your AI response here"
}
}
When to use: When directly passing OpenAI chat response. WordPress extracts message.content and wraps it in HTML.
Format 4: Simple Message
{
"message": "Processing complete!",
"success": true
}
When to use: For simple text responses. WordPress wraps the message in <p> tags.
Format 5: Error Response
{
"error": true,
"message": "Something went wrong",
"details": "Error details here"
}
When to use: When something fails. WordPress displays the error message to the user.
Real-World Examples (Copy & Paste Ready)
Example 1: Simple Text Response
n8n Code Node:
return {
output_html: `
<div class="ai-response">
<h3>✅ Success!</h3>
<p>Your request has been processed.</p>
</div>
`,
success: true
};
Example 2: AI Chatbot Response
n8n Code Node (after OpenAI):
// Get AI response from previous node
const aiReply = $json.choices[0].message.content;
return {
output_html: `
<div class="chat-response">
<div class="avatar">🤖</div>
<div class="message">${aiReply}</div>
</div>
`,
success: true
};
Example 3: Image Generation Response
n8n Code Node (after DALL-E):
// Get generated image URL
const imageUrl = $json.data[0].url;
return {
output_html: `
<div class="image-result">
<h3>🎨 Your AI Image is Ready!</h3>
<img src="${imageUrl}" alt="Generated Image"
style="max-width: 100%; border-radius: 10px; box-shadow: 0 4px 6px rgba(0,0,0,0.1);">
<p>
<a href="${imageUrl}" download class="download-btn">
Download Full Resolution
</a>
</p>
</div>
`,
success: true
};
Example 4: Multiple Results
n8n Code Node:
const formData = $json.body.form_data;
const userEmail = formData.field_email;
const description = formData.field_description;
return {
output_html: `
<div class="result-card">
<h3>📊 Processing Complete</h3>
<div class="info-section">
<strong>Email:</strong> ${userEmail}
</div>
<div class="info-section">
<strong>Your Request:</strong> ${description}
</div>
<div class="info-section">
<strong>Status:</strong> ✅ Complete
</div>
<p style="margin-top: 20px; padding: 15px; background: #f0f9ff; border-radius: 8px;">
We've sent detailed results to your email address.
</p>
</div>
`,
success: true,
display_preview: `Request from ${userEmail}`
};
Example 5: Error Handling
n8n Code Node (error scenario):
const formData = $json.body.form_data;
const description = formData.field_description;
// Validate input
if (!description || description.length < 10) {
return {
output_html: `
<div class="error-message">
<h3>⚠️ Invalid Input</h3>
<p>Please provide a description of at least 10 characters.</p>
<p>You entered: ${description ? description.length : 0} characters.</p>
</div>
`,
success: false,
error: true,
message: "Description too short"
};
}
// Process normally if valid
return {
output_html: `<div>Processing...</div>`,
success: true
};
Example 6: With Dynamic Data
n8n Code Node (using form data):
const formData = $json.body.form_data;
const topic = formData.field_topic || 'general topic';
const tone = formData.field_tone || 'professional';
const length = formData.field_length || '500';
// Simulate AI processing (replace with actual AI call)
const content = `This is your ${tone} content about ${topic}, approximately ${length} words.`;
return {
output_html: `
<div class="content-result">
<h3>✨ Your AI Content</h3>
<div class="metadata">
<span><strong>Topic:</strong> ${topic}</span> |
<span><strong>Tone:</strong> ${tone}</span> |
<span><strong>Length:</strong> ~${length} words</span>
</div>
<div class="content-body" style="margin-top: 20px; padding: 20px; background: white; border-radius: 8px;">
${content}
</div>
<button onclick="navigator.clipboard.writeText(this.previousElementSibling.textContent)"
style="margin-top: 15px; padding: 10px 20px; background: #3a79f7; color: white; border: none; border-radius: 5px; cursor: pointer;">
📋 Copy to Clipboard
</button>
</div>
`,
success: true
};
Common Response Mistakes & Fixes
❌ WRONG: Returning Plain Text
// This will NOT work
return "Your result here";
Fix:
// ✅ Correct
return {
output_html: "<div>Your result here</div>",
success: true
};
❌ WRONG: Missing Quotes in JSON
// Invalid JSON - will cause errors
return {
output_html: <div>Result</div>, // Missing quotes!
success: true
};
Fix:
// ✅ Correct
return {
output_html: "<div>Result</div>", // Proper quotes
success: true
};
❌ WRONG: HTML Without Wrapper
// Not wrapped in object
return "<div>Result</div>";
Fix:
// ✅ Correct
return {
output_html: "<div>Result</div>",
success: true
};
❌ WRONG: Forgetting to Escape HTML
// Unescaped quotes break JSON
return {
output_html: "<div class="result">Text</div>", // Breaks!
success: true
};
Fix:
// ✅ Correct - Use single quotes in HTML
return {
output_html: "<div class='result'>Text</div>",
success: true
};
// Or use template literals
return {
output_html: `<div class="result">Text</div>`,
success: true
};
❌ WRONG: Not Setting Content-Type
In custom API endpoints, you MUST set the header:
// PHP
header('Content-Type: application/json');
// Node.js (Express)
res.setHeader('Content-Type', 'application/json');
// Python (Flask)
return jsonify({...})
Testing Your Response Format
Step 1: Check Response in n8n
- Run your workflow in n8n
- Click the last node (Respond to Webhook)
- Check "Output" tab
- Verify you see:
{"output_html": "...", "success": true}
Step 2: Check Response in Browser
- Submit form on WordPress
- Press F12 → Network tab
- Find the
admin-ajax.phprequest - Click it → Response tab
- You should see your JSON response
Step 3: Validate JSON
- Copy your response from browser
- Go to JSONLint.com
- Paste and click "Validate JSON"
- Fix any syntax errors shown
Response Troubleshooting Guide
🔴 Problem: Blank Screen / No Response Shows
Causes:
- Missing
output_htmlfield - Invalid JSON syntax
- HTTP status not 200
- Content-Type not application/json
Debug Steps:
- Open browser DevTools (F12)
- Go to Console tab - check for JavaScript errors
- Go to Network tab - find admin-ajax.php request
- Check Response tab - verify JSON is valid
- Check Headers tab - verify Content-Type is application/json
- Check Status - should be 200
Quick Fix:
// In n8n Code node, use this template:
return {
output_html: `<div>Test response</div>`,
success: true
};
🔴 Problem: "Unexpected token < in JSON"
Cause: Webhook returned HTML instead of JSON (usually an error page)
Fix:
- Check n8n execution log for errors
- Verify workflow is active
- Make sure Respond to Webhook node returns JSON
- Check for 404/500 errors
🔴 Problem: Shows "Processing..." Forever
Causes:
- Webhook timeout (> 60 seconds)
- Network connection lost
- Workflow not returning response
Fix:
- Check workflow execution time in n8n
- Simplify workflow to return faster
- Add timeout handling in workflow
- Use async processing for long tasks
🔴 Problem: HTML Shows as Raw Text
Cause: HTML is being escaped/encoded
Fix: Ensure you're returning raw HTML in output_html, not escaped:
// ✅ Correct
output_html: "<div>Hello</div>"
// ❌ Wrong (escaped)
output_html: "<div>Hello</div>"
Response Best Practices
💡 Best Practices:
- ✅ Always include
success: truefor successful responses - ✅ Use template literals (backticks) for multi-line HTML
- ✅ Include inline styles for consistent appearance
- ✅ Add success/error icons (✅ ❌ ⚠️) for visual feedback
- ✅ Return user-friendly error messages, not technical errors
- ✅ Keep responses under 1MB (large images = slow)
- ✅ Test with empty/invalid inputs
- ✅ Log errors for debugging
Advanced: Custom Styling in Responses
Add inline CSS for beautiful responses:
return {
output_html: `
<div style="
max-width: 600px;
margin: 20px auto;
padding: 30px;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
border-radius: 15px;
box-shadow: 0 10px 40px rgba(0,0,0,0.2);
color: white;
font-family: system-ui, -apple-system, sans-serif;
">
<h2 style="margin: 0 0 15px 0; font-size: 24px;">
✨ AI Result
</h2>
<p style="font-size: 16px; line-height: 1.6; margin: 0;">
Your content here...
</p>
</div>
`,
success: true
};
output_html field containing your HTML. Test thoroughly!
Common Integrations
1. OpenAI GPT (Text Generation)
n8n Workflow: Webhook → OpenAI Chat → Code (Format) → Respond
Configuration:
- Model: gpt-4 or gpt-3.5-turbo
- Temperature: 0.7 (creative) or 0.3 (focused)
- Max Tokens: 500-2000
2. DALL-E (Image Generation)
n8n Workflow: Webhook → OpenAI Image → Code (Format) → Respond
Configuration:
- Size: 1024x1024, 1024x1792, or 1792x1024
- Quality: HD or Standard
- n: Number of images (1-4)
3. Anthropic Claude
n8n Workflow: Webhook → HTTP Request (Anthropic API) → Code (Parse) → Respond
4. Email with SendGrid
n8n Workflow: Webhook → SendGrid (Send Email) → Respond
5. Save to Airtable
n8n Workflow: Webhook → Airtable (Create Record) → Respond
6. Slack Notification
n8n Workflow: Webhook → Slack (Send Message) → Respond
Troubleshooting
Webhook Not Receiving Data
Check:
- ✅ Webhook URL is correct
- ✅ Workflow is activated
- ✅ No typos in URL
- ✅ Webhook accepts POST requests
- ✅ Check n8n execution history
Test with cURL:
curl -X POST https://your-webhook-url.com/endpoint \
-H "Content-Type: application/json" \
-d '{
"agent_id": 123,
"form_data": {
"field_test": "hello"
}
}'
Timeout Errors
Causes:
- Webhook takes > 60 seconds
- AI service is slow
- Network issues
Solutions:
- Optimize workflow - Remove unnecessary steps
- Use async processing - Return immediate response, email results
- Show loading state - Let users know it's processing
Response Not Showing
Check:
- Response format is correct JSON
- Contains
output_htmloroutputfield - Status code is 200
- No JavaScript errors in console
Debug in Browser:
- Open DevTools (F12)
- Go to Network tab
- Submit form
- Find
wp-admin/admin-ajax.phprequest - Check response
WordPress shows "Webhook error 4xx/5xx"
Starting with the 11 Nov 2025 update, the form surfaces the exact HTTP status and the first part of the webhook response.
Common errors:
Webhook error 410: Scenario inactive→ Turn the scenario ON or update the webhook URLWebhook error 500: Unexpected token...→ Check for malformed JSON in responseWebhook error 401/403→ Check auth headers or shared secrets
Best Practices
Security
✅ DO:
- Use HTTPS webhooks only
- Validate all input data
- Sanitize before using in AI prompts
- Rate limit webhook calls
- Log all requests
❌ DON'T:
- Expose API keys in responses
- Trust user input blindly
- Store sensitive data unencrypted
Performance
✅ Optimize:
- Keep workflows simple
- Cache common responses
- Use async processing for slow tasks
- Return responses quickly (<3s ideal)
User Experience
✅ Provide:
- Clear loading states
- Helpful error messages
- Success confirmations
- Progress indicators for long tasks