# Webhook validating issue

**URL:** <https://devforum.zoom.us/t/webhook-validating-issue/132046>\
**Category:** API and Webhooks\
**Created:** [April 28, 2025, 1:44pm UTC](https://devforum.zoom.us/t/webhook-validating-issue/132046 "2025-04-28T13:44:40Z")\
**Posts on this page:** 9\
**Page:** 1

<div class="post-metadata">

**Author:** ![That](https://sea2.discourse-cdn.com/flex016/user_avatar/devforum.zoom.us/that/32/68790_2.png) [@That](https://devforum.zoom.us/u/That)\
**Post date:** [April 28, 2025, 1:44pm UTC](https://devforum.zoom.us/t/webhook-validating-issue/132046/1 "2025-04-28T13:44:40Z")

</div>

I’m encountering an error while validating my Zoom webhook. The message displayed is:  
“URL validation failed. Try again later.”

I would like to raise a ticket for this issue. If there is an existing solution or guidance available, I would appreciate your assistance.

Below is the code I am currently using for the Zoom webhook endpoint:

Route::post(‘/zoom/test-webhook’, function (Request $request) {  
$webhookSecretToken = ‘MJJP4FUUSp6q7ePLlDfupA’;

```
$headers = $request->headers;
$zoomSignature = $headers->get('x-zm-signature');
$zoomTimestamp = $headers->get('x-zm-request-timestamp');

if (abs(time() - (int) $zoomTimestamp) > 300) {
    return response()->json(['message' => 'Timestamp is too old.'], 400);
}

$rawBody = $request->getContent();
$computedMessage = "v0:{$zoomTimestamp}:{$rawBody}";
$computedHash = hash_hmac('sha256', $computedMessage, $webhookSecretToken);
$computedSignature = "v0={$computedHash}";

Log::info('Zoom Webhook Signature Validation', [
    'computed' => $computedSignature,
    'received' => $zoomSignature,
    'body' => $rawBody
]);

if (!hash_equals($computedSignature, $zoomSignature)) {
    return response()->json(['message' => 'Invalid signature.'], 403);
}

$data = json_decode($rawBody, true);

if (isset($data['payload']['plainToken'])) {
    $plainToken = $data['payload']['plainToken'];
    $encryptedToken = hash_hmac('sha256', $plainToken, $webhookSecretToken);

    return response()->json([
        'plainToken' => $plainToken,
        'encryptedToken' => $encryptedToken
    ], 200);
}

return response()->noContent(204);

```

});

Let me know if any changes are needed in this code or if there’s a configuration I might be missing.

Thank you!

---

<div class="post-metadata">

**Author:** ![elisa.zoom](https://sea2.discourse-cdn.com/flex016/user_avatar/devforum.zoom.us/elisa.zoom/32/47836_2.png) [@elisa.zoom](https://devforum.zoom.us/u/elisa.zoom)\
**Post date:** [May 5, 2025, 8:24pm UTC](https://devforum.zoom.us/t/webhook-validating-issue/132046/2 "2025-05-05T20:24:01Z")

</div>

Hey @That  
Hope you are doing great!  
Have you been able to troubleshoot this on your end?

---

<div class="post-metadata">

**Author:** ![That](https://sea2.discourse-cdn.com/flex016/user_avatar/devforum.zoom.us/that/32/68790_2.png) [@That](https://devforum.zoom.us/u/That)\
**Post date:** [May 8, 2025, 9:21am UTC](https://devforum.zoom.us/t/webhook-validating-issue/132046/3 "2025-05-08T09:21:53Z")

</div>

Hello Elisa,

Yes, I’ve tried troubleshooting the issue, but I haven’t been able to identify the exact cause of the validation failure. When I click the **Validate** button in Zoom, I simply receive the message:  
**Please validate your endpoint URL.**

The validation fails with the message:  
**URL validation failed. Try again later.**

I’ve ensured that my code handles the plain token challenge as per Zoom’s documentation, and the endpoint is publicly accessible. However, the validation still doesn’t succeed.

If there’s any known solution, configuration detail, or guidance available for this issue, I’d appreciate your help. I’d also like to raise a support ticket if needed.

Thank you!

---

<div class="post-metadata">

**Author:** ![elisa.zoom](https://sea2.discourse-cdn.com/flex016/user_avatar/devforum.zoom.us/elisa.zoom/32/47836_2.png) [@elisa.zoom](https://devforum.zoom.us/u/elisa.zoom)\
**Post date:** [May 8, 2025, 2:33pm UTC](https://devforum.zoom.us/t/webhook-validating-issue/132046/4 "2025-05-08T14:33:59Z")

</div>

Hey @That  
Here is the link to our Docs with some code samples for validation

> **[Using webhooks](https://developers.zoom.us/docs/api/webhooks/#implement-the-challenge-response-check-flow)**
>
> The Zoom Developer Platform is an open platform that allows third-party developers to build applications and integrations upon Zoom’s video-first unified communications platform.

I am also linking a sample app that might help you as a reference:

> **[GitHub - zoom/webhook-sample: Receive Zoom webhooks.](https://github.com/zoom/webhook-sample)**
>
> Receive Zoom webhooks.

---

<div class="post-metadata">

**Author:** ![That](https://sea2.discourse-cdn.com/flex016/user_avatar/devforum.zoom.us/that/32/68790_2.png) [@That](https://devforum.zoom.us/u/That)\
**Post date:** [May 9, 2025, 5:18am UTC](https://devforum.zoom.us/t/webhook-validating-issue/132046/5 "2025-05-09T05:18:04Z")

</div>

We’ve implemented the Zoom webhook exactly as per the sample you provided, including signature validation and `plainToken` handling—you’re welcome to review our code above to confirm.

---

<div class="post-metadata">

**Author:** ![freelancer.nak](https://sea2.discourse-cdn.com/flex016/user_avatar/devforum.zoom.us/freelancer.nak/32/40968_2.png) [@freelancer.nak](https://devforum.zoom.us/u/freelancer.nak)\
**Post date:** [May 9, 2025, 12:06pm UTC](https://devforum.zoom.us/t/webhook-validating-issue/132046/6 "2025-05-09T12:06:39Z")

</div>

@That Hi,

It seems like you’re facing an issue with webhook validation. Here’s a working Next.js version of the webhook verification that could be useful. You just need to convert it into Laravel for your use case.

### Working Next.js Code:

```auto
import { HttpStatusCode } from 'axios';
const crypto = require('crypto');

export class WebhookVerifier {
    // Method to verify the signature
    static verifySignature({ zmSignature, zmRequestTimeStamp, body }) {
        const message = `v0:${zmRequestTimeStamp}:${JSON.stringify(body)}`;
        const hmac = crypto.createHmac('sha256', process.env.ZOOM_EVENT_VERIFICATION_SECRET);
        const computedHash = hmac.update(message).digest('hex');
        const signature = `v0=${computedHash}`;

        // Compare computed signature with Zoom signature
        return signature === zmSignature;
    }

    // Method to process the webhook verification response
    static async verifyEventEndpoint({ plainToken }) {
        try {
            const secret = process.env.ZOOM_EVENT_VERIFICATION_SECRET;

            // Encrypt plainToken
            const encryptedToken = crypto
                .createHmac('sha256', secret)
                .update(plainToken)
                .digest('hex');

            return {
                status: HttpStatusCode.Ok,
                payload: {
                    plainToken: plainToken,
                    encryptedToken: encryptedToken,
                },
            };
        } catch (error) {
            console.error(error);
            return { status: 500, message: 'Internal Server Error' };
        }
    }
}

```

### Helper to Get Payload and Verify Signature:

```auto
class ZoomWebhookService {
    static async notification({ req, meta }) {
        try {
            const body = await req.json();
            const zmRequestTimeStamp = req.headers.get("x-zm-request-timestamp");
            const zmSignature = req.headers.get("x-zm-signature");

            if (WebhookVerifier.verifySignature({ zmSignature, zmRequestTimeStamp, body })) {
                const { event, payload } = body;

                if (event === 'endpoint.url_validation') {
                    const { plainToken } = payload;
                    return WebhookVerifier.verifyEventEndpoint({ plainToken });
                }

                console.log(`-- zoom event: ${event} --`);

                return this.response({
                    payload: { message: "Accepted" },
                    status: HttpStatusCode.Ok,
                });
            }

            return this.response({
                payload: { message: "Unauthorized" },
                status: HttpStatusCode.Unauthorized,
            });
        } catch (error) {
            console.error("-- Webhook Error --", error.message);
            return this.response({
                payload: { message: "Internal Error" },
                status: HttpStatusCode.InternalServerError,
            });
        }
    }
}

```

### Key Notes:

- The code verifies the signature by using the raw body and headers to check the validity of the webhook.
- After successful validation, it processes events like `url_validation` and responds accordingly.
- This is designed for Next.js but can be easily adapted to a Laravel environment.

You can convert this code to Laravel by using Laravel’s `Request` class for getting the raw body and headers, and then implementing the signature verification using Laravel’s built-in cryptographic functions.

---

<div class="post-metadata">

**Author:** ![Stanislav](https://sea2.discourse-cdn.com/flex016/user_avatar/devforum.zoom.us/stanislav/32/69041_2.png) [@Stanislav](https://devforum.zoom.us/u/Stanislav)\
**Post date:** [May 9, 2025, 12:20pm UTC](https://devforum.zoom.us/t/webhook-validating-issue/132046/7 "2025-05-09T12:20:36Z")

</div>

Thanks for the feedback.  
but I have no problems with the handler.  
I passed the validation.

The problem is that the web hooks do not come to the server.  
Not that I can’t process them.

---

<div class="post-metadata">

**Author:** ![Marketing10](https://avatars.discourse-cdn.com/v4/letter/m/c5a1d2/32.png) [@Marketing10](https://devforum.zoom.us/u/Marketing10)\
**Post date:** [January 21, 2026, 3:10pm UTC](https://devforum.zoom.us/t/webhook-validating-issue/132046/8 "2026-01-21T15:10:38Z")

</div>

Hi everyone,

I’m reaching out for assistance with a sudden issue regarding our webhook event notifications. Our webhook subscription for both **dev** and **prod** environments was automatically disabled on **January 10, 2026**. Prior to this, the system had been working perfectly for several months without any changes to our configuration.

**The Issue:** Currently, our event subscriptions show as “Disabled,” and we are prompted to re-validate the endpoint URL.

**What we have tested:**

1. **Manual Test (Postman):** We tested our endpoint (`https://dev-api.guseip.io/gus/zoom/event`) directly in Postman using the same Token Authentication credentials. The API works perfectly fine and returns the expected successful response.

2. **Zoom Marketplace Console Validation:** When we try to click the **“Validate”** button in the Zoom App Marketplace console, we receive an error stating: **“Fail to get access token”**.

**Technical Details:**

- **Authentication Method:** Token Authentication.

- **Token URL:** `https://dev-authprovider.apphero.io/oauth2/token?grant_type=client_credentials`.

- **Handshake logic:** Our backend is configured to handle the HMAC-SHA256 Challenge-Response check (CRC).

- **Timing:** The issue started abruptly on Jan 10.

We are confused as to why the Zoom console is reporting a failure to retrieve an access token when the exact same credentials and URL work in Postman. This prevents us from re-validating the endpoint to resume receiving events.

Has anyone encountered this specific “fail to get access token” error during validation while manual tests succeed? Are there any new requirements for Token Authentication or account-level blocks that could have been triggered on Jan 10?

Any guidance on how to resolve this would be greatly appreciated.

Thank you, Surya Prakash

---

<div class="post-metadata">

**Author:** ![elisa.zoom](https://sea2.discourse-cdn.com/flex016/user_avatar/devforum.zoom.us/elisa.zoom/32/47836_2.png) [@elisa.zoom](https://devforum.zoom.us/u/elisa.zoom)\
**Post date:** [January 28, 2026, 2:28pm UTC](https://devforum.zoom.us/t/webhook-validating-issue/132046/9 "2026-01-28T14:28:10Z")

</div>

hi @Marketing10  
Thanks for reaching out to us!  
Can you please confirm if you are still having this issue? I have not seen this error in the past and it is strange since it was working on your end.
