Send change events to any HTTPS endpoint — Zapier, n8n, Make, custom CRMs, internal tools, or your own server. Available on Pro and Business plans.
When Diffwatch detects a change on a watched URL, it POSTs a JSON payload to every enabled webhook channel for that watch. The payload schema is stable — new fields may be added, but existing fields will not be renamed or removed.
Every change event delivers this JSON object:
{
"event": "change_detected",
"watch": {
"id": 12345,
"url": "https://example.com/pricing",
"selector_type": "css_selector", // full_page | css_selector | text_contains | regex
"selector_value": ".price", // null when selector_type is full_page
"check_interval_minutes": 5
},
"change": {
"id": 67890,
"before_hash": "sha256hex...", // may be null for first detection
"after_hash": "sha256hex...",
"ai_summary": "Pricing plan changed from $9 to $12/mo",
"diff_excerpt": "...first 300 chars of new content...",
"detected_at": "2026-06-14T12:00:00.000Z"
},
"manage_url": "https://diffwatch-ai.polsia.app/manage?token=..."
}
| Field | Type | Description |
|---|---|---|
| event | string | Always "change_detected" for alert events. |
| watch.id | number | Diffwatch internal watch ID. |
| watch.url | string | The monitored URL. |
| watch.selector_type | string | full_page | css_selector | text_contains | regex |
| watch.selector_value | string|null | The CSS selector, text, or regex pattern. null for full-page watches. |
| watch.check_interval_minutes | number | How often the URL is checked. |
| change.id | number|null | Change event ID for deduplication. |
| change.ai_summary | string|null | One-sentence AI description of what changed. |
| change.diff_excerpt | string|null | First 300 characters of the new content. |
| change.detected_at | string (ISO 8601) | Timestamp when the change was detected. |
| manage_url | string | Signed manage link for the watch owner. |
Every webhook request includes:
Content-Type: application/json
User-Agent: Diffwatch/1.0
X-Diffwatch-Signature: sha256=<hex> (only when a secret is configured)
X-Diffwatch-Signature. Verify it in your receiver to reject spoofed requests.
const crypto = require('crypto'); function verifyDiffwatchSignature(rawBody, secret, signatureHeader) { if (!signatureHeader) return false; const expected = 'sha256=' + crypto .createHmac('sha256', secret) .update(rawBody) .digest('hex'); // Constant-time comparison to prevent timing attacks return crypto.timingSafeEqual( Buffer.from(signatureHeader), Buffer.from(expected) ); } // Express example app.post('/webhook', express.raw({ type: '*/*' }), (req, res) => { const sig = req.headers['x-diffwatch-signature']; if (!verifyDiffwatchSignature(req.body, process.env.WEBHOOK_SECRET, sig)) { return res.status(401).send('Invalid signature'); } const event = JSON.parse(req.body); console.log('Change detected:', event.change.ai_summary); res.sendStatus(200); });
import hmac, hashlib def verify_diffwatch_signature(raw_body: bytes, secret: str, signature_header: str) -> bool: if not signature_header: return False expected = 'sha256=' + hmac.new( secret.encode(), raw_body, hashlib.sha256 ).hexdigest() return hmac.compare_digest(signature_header, expected) # Flask example from flask import Flask, request, abort app = Flask(__name__) @app.route('/webhook', methods=['POST']) def handle_webhook(): sig = request.headers.get('X-Diffwatch-Signature', '') if not verify_diffwatch_signature(request.get_data(), 'your-secret', sig): abort(401) event = request.json print('Change:', event['change']['ai_summary']) return '', 200
Diffwatch sends one request per change event. If your endpoint returns a 5xx response, we retry once immediately. If the retry also fails, the event is logged as failed and not retried again — ensuring your endpoint isn't flooded. 4xx responses are treated as permanent failures (misconfigured endpoint) and are not retried.
change.ai_summary, watch.url, etc. to your Zap action.Diffwatch enforces a 10-minute dedup window per watch — you'll receive at most one alert every 10 minutes per watch, even if the page changes multiple times within that window.