Webhook validating issue

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!

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

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!

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

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

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.

@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:

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:

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.

1 Like

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.

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

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.