Skip to main content
Glama
Hookflo
by Hookflo

verify_signature

Verify webhook signatures and diagnose failures with static header/body analysis or live endpoint testing to identify and fix verification issues.

Instructions

Verify and debug webhook signatures. Two modes: (1) Static — pass raw headers, body and secret, get exact error with fix. (2) Live — pass your endpoint URL, Tern sends a real signed test payload and diagnoses exactly why it failed.

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault
platformYesWebhook provider platform
secretNoWebhook signing secret. Leave empty for fal.ai.
headersNoRaw request headers — for static verification mode
bodyNoRaw request body string — for static verification mode
endpointUrlNoYour live webhook endpoint URL — for live diagnosis mode

Implementation Reference

  • The primary handler function 'verifySignature' for the tool. It either performs a live diagnosis by hitting an endpoint or verifies headers/body against a secret using WebhookVerificationService.
    export async function verifySignature(input: VerifySignatureInput) {
      if (input.endpointUrl) {
        try {
          const body = getTestPayload(input.platform)
          const headers = await buildSignedRequest(input.platform, input.secret, body)
    
          const response = await fetch(input.endpointUrl, {
            method: 'POST',
            headers,
            body,
          })
    
          const responseText = await response.text().catch(() => '')
    
          if (response.ok) {
            return {
              mode: 'live_endpoint',
              valid: true,
              statusCode: response.status,
              endpoint: input.endpointUrl,
              message: `✓ Endpoint verified successfully. ${input.platform} webhook signature accepted.`,
              response: responseText,
            }
          }
    
          const diagnosis: Record<number, string> = {
            400: 'Endpoint returned 400 — signature verification is failing. Most likely cause: raw body is being parsed before verification.',
            401: 'Endpoint returned 401 — unauthorized. Secret mismatch or missing signature header.',
            403: 'Endpoint returned 403 — forbidden. Signature rejected. Check your secret matches the platform dashboard.',
            404: 'Endpoint returned 404 — route not found. Check your webhook URL path is correct.',
            500: 'Endpoint returned 500 — server error in your handler. Check your application logs.',
          }
    
          return {
            mode: 'live_endpoint',
            valid: false,
            statusCode: response.status,
            endpoint: input.endpointUrl,
            diagnosis: diagnosis[response.status] ?? `Unexpected status ${response.status}`,
            response: responseText,
            fix: response.status === 400 || response.status === 403
              ? 'Add raw body parsing before your verification step. See tern.hookflo.com for framework-specific guides.'
              : null,
          }
        } catch (error) {
          return {
            mode: 'live_endpoint',
            valid: false,
            error: (error as Error).message,
            diagnosis: 'Could not reach endpoint. Check the URL is publicly accessible and your server is running.',
          }
        }
      }
    
      if (!input.headers || !input.body) {
        return {
          valid: false,
          error: 'Provide either headers + body for static verification, or endpointUrl for live diagnosis.',
        }
      }
    
      try {
        const request = new Request('https://tern-mcp.local/webhook', {
          method: 'POST',
          headers: input.headers,
          body: input.body,
        })
    
        const result = await WebhookVerificationService.verifyWithPlatformConfig(
          request,
          input.platform,
          input.secret,
        )
    
        if (result.isValid) {
          return {
            mode: 'static',
            valid: true,
            platform: result.platform,
            eventId: result.eventId,
            payload: result.payload,
            message: `✓ Signature verified for ${input.platform}`,
          }
        }
    
        return {
          mode: 'static',
          valid: false,
          platform: result.platform,
          errorCode: result.errorCode,
          error: result.error,
          explanation: ERROR_EXPLANATIONS[result.errorCode ?? ''] ?? 'Unknown error. Check secret and headers.',
          debugTips: DEBUG_TIPS[result.errorCode ?? ''] ?? [],
        }
      } catch (error) {
        return {
          mode: 'static',
          valid: false,
          error: (error as Error).message,
          explanation: 'Verification threw unexpected error. Check your inputs.',
        }
      }
    }
  • Zod schema defining the input parameters for the 'verify_signature' tool.
    export const verifySignatureSchema = z.object({
      platform: z.enum([
        'stripe', 'github', 'clerk', 'shopify', 'polar', 'workos',
        'dodopayments', 'paddle', 'lemonsqueezy', 'gitlab', 'sentry',
        'grafana', 'doppler', 'sanity', 'falai', 'replicateai',
      ]).describe('The webhook provider platform'),
      secret: z.string()
        .optional()
        .default('')
        .describe('Webhook signing secret. Leave empty for fal.ai (auto JWKS)'),
    
      headers: z.record(z.string())
        .optional()
        .describe('Raw request headers as key-value object'),
      body: z.string()
        .optional()
        .describe('Raw request body as string — must be raw, not parsed JSON'),
    
      endpointUrl: z.string()
        .optional()
        .describe('Your webhook endpoint URL. If provided, Tern sends a real signed test payload and diagnoses the response.'),
    })
    
    export type VerifySignatureInput = z.infer<typeof verifySignatureSchema>

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/Hookflo/tern-mcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server