HEX
Server: Apache/2.4.46 (Win64) OpenSSL/1.1.1j PHP/8.4.25
System: Windows NT DESKTOP-4TAV2RJ 10.0 build 19045 (Windows 10) AMD64
User: fred (0)
PHP: 8.4.25
Disabled: NONE
Upload Files
File: C:/Users/fred/.codex/.tmp/plugins/plugins/zoom/skills/webhooks/references/verification.md
# Webhooks - Verification

Verify webhook authenticity and handle URL validation.

## Overview

Zoom provides two verification mechanisms:
1. **URL Validation** - Verify your endpoint during setup
2. **Request Signature** - Verify each webhook request

## URL Validation

When you configure a webhook endpoint, Zoom sends a validation request:

```json
{
  "event": "endpoint.url_validation",
  "payload": {
    "plainToken": "random_token_string"
  }
}
```

### Response Required

Hash the token with your webhook secret and respond:

```javascript
const crypto = require('crypto');

app.post('/webhook', (req, res) => {
  const { event, payload } = req.body;

  if (event === 'endpoint.url_validation') {
    const hashForValidation = crypto
      .createHmac('sha256', WEBHOOK_SECRET_TOKEN)
      .update(payload.plainToken)
      .digest('hex');

    return res.json({
      plainToken: payload.plainToken,
      encryptedToken: hashForValidation
    });
  }

  // Handle other events...
  res.status(200).send();
});
```

## Request Signature Verification

Verify each webhook request is from Zoom:

### Headers

| Header | Description |
|--------|-------------|
| `x-zm-signature` | Request signature |
| `x-zm-request-timestamp` | Request timestamp |

### Verification Code

```javascript
const crypto = require('crypto');

function verifyWebhook(req) {
  const signature = req.headers['x-zm-signature'];
  const timestamp = req.headers['x-zm-request-timestamp'];
  // Prefer raw body bytes captured by your framework to avoid JSON re-serialization mismatches.
  const body = req.rawBody ? req.rawBody.toString('utf8') : JSON.stringify(req.body);

  const message = `v0:${timestamp}:${body}`;
  const hash = crypto
    .createHmac('sha256', WEBHOOK_SECRET_TOKEN)
    .update(message)
    .digest('hex');

  const expectedSignature = `v0=${hash}`;

  return signature === expectedSignature;
}
```

## Security Best Practices

1. Always verify signatures
2. Check timestamp to prevent replay attacks
3. Use HTTPS endpoints only
4. Keep webhook secret secure

## Resources

- **Webhook verification**: https://developers.zoom.us/docs/api/rest/webhook-reference/#verify-webhook-events