Why webhooks over polling?
The naive approach to monitoring planning applications is to poll the API every hour or day and compare results. This works, but it's wasteful and slow — you're making hundreds of requests to get the same "nothing new" response.
Webhooks flip the model: instead of you asking "anything new?", PlanWire tells you the moment something new appears that matches your filter. Your endpoint receives a POST request with the full application data. No polling required.
What you'll build
A small Express/Node.js server that:
- Receives POST requests from PlanWire when new planning applications are found
- Verifies the HMAC signature to confirm the request is genuine
- Posts a message to Slack (or triggers whatever action you need)
You'll need: a PlanWire API key (free tier works), and a publicly accessible HTTPS endpoint. For local dev, use ngrok.
Step 1: Get your API key
Sign up at planwire.io/#signup — enter your email and you'll get a key immediately. No credit card needed for the free tier.
Step 2: Create a webhook
Register your webhook endpoint with the councils and criteria you want to monitor:
curl -X POST "https://api.planwire.io/v1/webhooks" \
-H "X-API-Key: your_api_key" \
-H "Content-Type: application/json" \
-d '{
"url": "https://your-server.com/webhook/planning",
"filters": {
"postcode_prefix": "SW1",
"application_type": "Householder"
}
}'The response includes a secret — save this. You'll use it to verify incoming webhook requests.
Available filters:
council_id— specific council (e.g.camden)postcode_prefix— e.g.SW1orM1application_type—Householder,Full,Listed Building, etc.status—Pending,Approved,Refused
Step 3: Build the receiver
Here's a minimal Node.js/Express server that receives and verifies PlanWire webhooks:
import express from 'express'; import crypto from 'node:crypto'; const app = express(); const WEBHOOK_SECRET = process.env.PLANWIRE_WEBHOOK_SECRET; // Must use raw body for signature verification app.use('/webhook', express.raw({ type: 'application/json' })); app.post('/webhook/planning', (req, res) => { const sig = req.headers['x-planwire-signature']; const expected = crypto .createHmac('sha256', WEBHOOK_SECRET) .update(req.body) .digest('hex'); if (sig !== expected) { return res.status(401).json({ error: 'Invalid signature' }); } const payload = JSON.parse(req.body); const { event, application } = payload; console.log(`New planning application: ${application.reference}`); console.log(`Address: ${application.address}`); console.log(`Type: ${application.application_type}`); console.log(`Status: ${application.status}`); console.log(`Council: ${application.council_id}`); // Post to Slack (optional) if (process.env.SLACK_WEBHOOK_URL) { fetch(process.env.SLACK_WEBHOOK_URL, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ text: `*New planning application* in ${application.council_id}\n` + `*Ref:* ${application.reference}\n` + `*Address:* ${application.address}\n` + `*Type:* ${application.application_type}\n` + `*Status:* ${application.status}\n` + `<${application.url}|View on council portal>`, }), }); } res.json({ received: true }); }); app.listen(3001, () => console.log('Webhook receiver on :3001'));
Step 4: Test locally with ngrok
# Terminal 1: start your server PLANWIRE_WEBHOOK_SECRET=your_secret node webhook-receiver.js # Terminal 2: expose it publicly ngrok http 3001
Copy the ngrok HTTPS URL and update your webhook URL via the API (DELETE + re-create, or PATCH if needed).
Step 5: Verify it's working
You can trigger a test delivery from PlanWire (coming soon) or simply wait for the next data import cycle. You'll receive a POST with this shape:
{
"event": "application.new",
"application": {
"id": "f3a2b1c0-...",
"council_id": "camden",
"reference": "2025/0234/P",
"address": "12 Fitzroy Road, London, NW1 8TX",
"postcode": "NW1 8TX",
"application_type": "Householder",
"status": "Pending",
"description": "Single storey rear extension",
"received_date": "2025-03-05",
"url": "https://...",
"lat": 51.5416,
"lng": -0.1361
}
}Practical use cases
- Architects: alert when a householder extension is submitted within 2km of your studio, so you can reach out before they appoint a designer
- Property developers: monitor for large full-planning applications in target postcodes
- Planning consultants: track applications at specific sites you're advising on
- Researchers: pipe new applications into a database for longitudinal analysis
- PropTech products: power a real-time planning alert feature for your users
Deploying to production
Your webhook receiver needs to be publicly accessible via HTTPS. The simplest options are Railway, Fly.io, or Vercel (if you rewrite it as a serverless function). Make sure to set the PLANWIRE_WEBHOOK_SECRET environment variable — never hardcode it.
What's next
You can register multiple webhooks with different filters. Want approvals in Manchester AND new householder applications in Bristol? Create two separate webhooks, each with their own filter set.
See the full webhook API reference in the PlanWire docs.