localhost पर Zoom webhooks टेस्ट करने के लिए अपनी local POST route को npx portpreview PORT से expose करें, उस public HTTPS route को event notification endpoint के रूप में डालें और Validate पर क्लिक करने से पहले Zoom का endpoint.url_validation challenge implement करें। सामान्य events के लिए untouched request body और timestamp से x-zm-signature verify करें, event को idempotent तरीके से persist करें और तीन सेकंड के अंदर 2xx लौटाएँ।
Zoom event subscriptions localhost तक कैसे पहुँचती हैं
Zoom webhooks, Meetings, Webinars, Phone, Team Chat, Rooms और आपके app के लिए उपलब्ध दूसरी services के subscribed events की JSON HTTP POST notifications हैं। सही event catalog और fields app type, enabled products, account entitlements, scopes और मौजूदा Zoom platform पर निर्भर करते हैं। केवल वही events चुनें जिन्हें आपका handler समझता है और app build flow में दिखाया गया current event schema इस्तेमाल करें।
आपका endpoint publicly accessible HTTPS होना चाहिए, जिसमें fully qualified domain name, मान्य CA-issued certificate chain, TLS 1.2 या उसके बाद का version और JSON POST requests का support हो। http://localhost:3000 जैसा loopback URL इन requirements को पूरा नहीं कर सकता। PortPreview public HTTPS edge उपलब्ध कराता है और requests को आपके local process तक forward करता है।
Endpoint requirements, challenge-response validation, event signatures, delivery behavior और मौजूदा configuration steps के लिए Zoom की official webhook documentation ही सबसे भरोसेमंद source है।
Raw body सुरक्षित रखने वाली Express route बनाएँ
Zoom की request signature body के बिल्कुल सही text पर बनती है। किसी JSON middleware के bytes को parse और serialize करने से पहले उन्हें capture करें। नीचे दिया example validation और सामान्य event verification, दोनों को एक route में handle करता है:
import crypto from 'node:crypto';
import express from 'express';
const app = express();
app.post('/webhooks/zoom', express.raw({ type: 'application/json', limit: '1mb' }), async (req, res) => {
const rawBody = req.body.toString('utf8');
let event;
try {
event = JSON.parse(rawBody);
} catch {
return res.status(400).json({ error: 'Invalid JSON' });
}
const secret = process.env.ZOOM_WEBHOOK_SECRET_TOKEN;
if (!secret) return res.sendStatus(500);
if (event.event === 'endpoint.url_validation') {
const plainToken = event.payload?.plainToken;
if (typeof plainToken !== 'string') return res.sendStatus(400);
const encryptedToken = crypto
.createHmac('sha256', secret)
.update(plainToken)
.digest('hex');
return res.status(200).json({ plainToken, encryptedToken });
}
const timestamp = req.get('x-zm-request-timestamp') ?? '';
const received = req.get('x-zm-signature') ?? '';
const message = `v0:${timestamp}:${rawBody}`;
const expected = `v0=${crypto
.createHmac('sha256', secret)
.update(message)
.digest('hex')}`;
const a = Buffer.from(received);
const b = Buffer.from(expected);
const valid = a.length === b.length && crypto.timingSafeEqual(a, b);
if (!valid) return res.sendStatus(401);
const age = Math.abs(Date.now() / 1000 - Number(timestamp));
if (!Number.isFinite(age) || age > 300) return res.sendStatus(401);
const requestId = req.get('x-zm-request-id');
const deliveryKey = requestId || crypto.createHash('sha256').update(rawBody).digest('hex');
await saveWebhookOnce({ provider: 'zoom', deliveryKey, event });
return res.sendStatus(200);
});
app.listen(3000);
इस example की पाँच मिनट की freshness window application security policy है, HMAC verification का विकल्प नहीं। ऐसी tolerance चुनें जो आपकी clock synchronization और delivery expectations के हिसाब से सही हो। Failures के लिए केवल reason category log करें, secret token या पूरी request body कभी नहीं।
Local tunnel शुरू करें
- Application चलाएँ और confirm करें कि route port 3000 पर local POST स्वीकार कर रही है।
- दूसरा terminal खोलकर
npx portpreview 3000चलाएँ। 3000 की जगह वह port दें जो आपका app वास्तव में इस्तेमाल करता है। - Generated origin के आगे route जोड़ें, जैसे
https://YOUR-TUNNEL.portpreview.dev/webhooks/zoom। - Validation और event tests के दौरान app और tunnel processes चलते रहने दें।
नए tunnel का hostname अलग होने पर Zoom उसे अलग endpoint मानता है। Events आने की उम्मीद करने से पहले नया URL update और validate करें। URL सीधे POST handler तक पहुँचना चाहिए; redirects भरोसेमंद webhook delivery के लिए ठीक नहीं हैं और Zoom 3xx responses को retry नहीं करता।
Zoom में event subscription जोड़ें
Zoom App Marketplace में अपना बनाया हुआ app खोलें और current build flow में दिखाई गई Features या Access area पर जाएँ। Event Subscriptions enable करें, subscription जोड़ें, उसके event types और receiver चुनें, फिर पूरा HTTPS endpoint URL paste करें। उपलब्ध receiver options और events app type तथा account configuration के अनुसार बदलते हैं। Subscriptions बदलने पर published apps को दोबारा review की जरूरत पड़ सकती है।
App से जुड़ा webhook secret token किसी ignored local environment variable, जैसे ZOOM_WEBHOOK_SECRET_TOKEN, में रखें। यह OAuth client secret, access token या deprecated verification token नहीं है। Environment बदलने के बाद local server restart करें।
Endpoint URL validation सही तरीके से implement करें
Validate पर क्लिक करने पर Zoom एक POST भेजता है, जिसका event, endpoint.url_validation होता है। Payload में plainToken मिलता है। Webhook secret token को key और plain token को message बनाकर HMAC SHA-256 calculate करें, digest को lowercase hexadecimal में encode करें और unchanged plainToken तथा बने हुए encryptedToken, दोनों वाला JSON response दें।
const encryptedToken = createHmac('sha256', webhookSecret)
.update(event.payload.plainToken)
.digest('hex');
return {
plainToken: event.payload.plainToken,
encryptedToken
};
तीन सेकंड के अंदर HTTP 200 और JSON body लौटाएँ। पूरी validation request को hash न करें, OAuth client secret इस्तेमाल न करें, digest को Base64-encode न करें और validation digest के आगे v0= न लगाएँ। ये अलग flows से जुड़े हैं। Initial validation सफल होने तक endpoint save नहीं किया जा सकता।
Zoom की current documentation हर 72 घंटे में automatic revalidation भी बताती है। बार-बार revalidation fail होने पर app owner को notifications भेजी जाती हैं; लगातार छह failures के बाद Zoom event subscription disable करके events रोक देता है। इसलिए बंद हो चुका development tunnel बाद की validation में fail होगा। Testing के बाद temporary subscriptions हटाएँ और production challenge handling को हमेशा available रखें।
सामान्य Zoom webhook requests verify करें
URL validation साबित करती है कि challenge के समय endpoint को secret पता है। सामान्य event verification अलग से साबित करती है कि received body, Zoom की भेजी HMAC से match करती है। x-zm-request-timestamp पढ़ें और यह exact message बनाएँ:
v0:{x-zm-request-timestamp}:{raw request body}
Webhook secret token को key बनाकर उस message का HMAC SHA-256 hash निकालें, digest को hexadecimal में encode करें, उसके आगे v0= लगाएँ और constant-time comparison से उसे x-zm-signature के साथ compare करें। Body original request body ही होनी चाहिए। JSON parse करके फिर JSON.stringify चलाने से whitespace या property formatting बदल सकती है और valid signature टूट सकती है।
Business logic चलाने से पहले missing, malformed, invalid या बहुत पुरानी signatures reject करें। System time synchronized रखें। पुराना webhook verification token deprecated था और जून 2025 में sunset होना तय था; नए code में Zoom का documented secret-token HMAC flow इस्तेमाल करें, किसी पुराने tutorial से copy किया गया Authorization equality check नहीं। Raw-body और timing-safe comparison की details के लिए webhook signature verification guide देखें।
Provider-specific event types dispatch करें
Verified payload पर भी schema validation और authorization checks जरूरी हैं। Meeting events में meeting ID और UUID जैसे identifiers के अलग काम हैं; repeated या recurring meetings के लिए UUID-level correlation महत्वपूर्ण है। Payload fields को event-specific मानें और हर subscription के लिए current reference follow करें।
async function processZoomEvent(event) {
switch (event.event) {
case 'meeting.started':
await markMeetingStarted({
uuid: event.payload.object.uuid,
startedAt: event.payload.object.start_time
});
break;
case 'meeting.ended':
await markMeetingEnded({
uuid: event.payload.object.uuid,
endedAt: event.payload.object.end_time
});
break;
default:
await recordUnhandledZoomEvent(event.event);
}
}
Event order को transaction log न मानें। Network delay, retries और parallel processing के कारण arrival order अप्रत्याशित हो सकता है। Provider event timestamp store करें और जहाँ सही हो वहाँ monotonic state rules लागू करें। Unknown event types को safe persistence के बाद observable बनाकर acknowledge करें, endpoint को बार-बार fail न कराएँ।
तीन सेकंड की delivery deadline पूरी करें
Successful delivery के लिए Zoom तीन सेकंड के अंदर HTTP 200 या 204 चाहता है। Request verify करें, minimal envelope validate करें, durable inbox या queue में लिखें और response लौटाएँ। Video processing, CRM updates, calendar calls, email और analytics workers में होने चाहिए।
Zoom की current notification-delivery documentation के अनुसार eligible server और connection failures को तीन बार retry किया जाता है: initial attempt के लगभग पाँच मिनट बाद, फिर उस retry के 20 मिनट बाद और second retry के 60 मिनट बाद। Zoom 2xx को success मानता है; वह 3xx redirects या 4xx client errors retry नहीं करता। Policies बदल सकती हैं, इसलिए exact intervals पर operational alerts बनाने से पहले official page दोबारा check करें।
हर event को idempotent बनाएँ
पहली attempt commit होने के बाद ambiguous timeout के कारण retry आ सकती है। Side effects से पहले deduplicate करें। Zoom की documented request structure में x-zm-request-id header मिलता है, लेकिन products या versions में अंतर होने पर code उसकी absence भी handle करे। मौजूद हो तो इसे इस्तेमाल करें; वरना verified immutable event data या verified raw body के cryptographic digest से stable key बनाएँ। Uniqueness को storage में enforce करें, केवल in-memory cache में नहीं।
await db.transaction(async (tx) => {
const claimed = await tx.webhookInbox.insertOnce({
provider: 'zoom',
deliveryKey,
eventType: event.event,
payload: event
});
if (!claimed) return;
await tx.jobs.enqueue({ type: 'process-zoom-event', deliveryKey });
});
Inbox claim और job creation को atomic रखें। Worker fail हो तो Zoom से redelivery माँगने के बजाय job retry करें। Inbox tables, unique keys और side-effect boundaries के लिए retry और idempotency guide देखें।
Validation और delivery समस्याएँ troubleshoot करें
Validate failure दिखाता है
Check करें कि URL public HTTPS है, उसमें exact route है, redirect नहीं है और वह active local port तक पहुँच रहा है। Confirm करें कि response HTTP 200 JSON है, जिसमें original plain token और केवल उसी token का lowercase hexadecimal HMAC है। Total response time मापें; validation तीन सेकंड में पूरी होनी चाहिए।
हर सामान्य event की signature verification fail होती है
पक्का करें कि आपने webhook secret token copy किया है, OAuth client secret या legacy verification token नहीं। JSON middleware से पहले body को raw bytes के रूप में capture करें, exact timestamp header इस्तेमाल करें, v0:timestamp:body message में दोनों colons रखें और केवल final event signature के आगे v0= लगाएँ।
Validation चलती है लेकिन events नहीं आते
देखें कि subscription enabled और saved है, सही event types और receiver चुने गए हैं और आपका account या users वे events generate कर रहे हैं। Revalidation status और app publication requirements check करें। Confirm करें कि validation के बाद tunnel URL नहीं बदला।
Handler सफल है फिर भी Zoom retry करता है
सिर्फ local logs नहीं, public response latency और status देखें। Slow work eventually पूरा होने पर भी तीन सेकंड की limit पार कर सकता है। जल्दी persist करें, 2xx लौटाएँ और asynchronously process करें। Duplicate-safe inbox retry को side effect दोहराने से रोकती है।
Captured event बाद में replay करने पर fail होता है
Freshness check को पुराना timestamp reject करना चाहिए और JSON में बदलाव HMAC invalid कर देता है। End-to-end tests के लिए नया provider event लें। Business-logic tests के लिए sanitized parsed fixture store करें और केवल test harness में ingress verification bypass करने के बाद dispatcher call करें। यह फर्क webhook replay guide में समझाया गया है।
Zoom webhook testing की security checklist
- Webhook secret tokens ignored environment files में रखें और exposed credentials rotate करें।
- किसी payload field पर भरोसा करने से पहले raw body के against HMAC verify करें।
- Timestamp tolerance enforce करें और replay risk घटाने के लिए server clock synchronize करें।
- Event type, account context, object identifiers, content type और body size validate करें।
- Logs से participant names, email addresses, meeting topics, chat content और recording data redact करें।
- जहाँ app configuration अनुमति दे, development और production के endpoints या secrets अलग रखें।
- Local session खत्म होने पर temporary public URLs और subscriptions हटाएँ।
Production-ready pattern वही है जो local में साबित हुआ: stable HTTPS ingress, permanent challenge handling, raw-body HMAC verification, durable idempotency, तीन सेकंड से कम में acknowledgement और isolated workers। सामान्य request tracing और route checks के लिए local webhook debugging guide follow करें।
