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>
Behavior4/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

With no annotations provided, the description carries the full burden of behavioral disclosure. It effectively describes key behaviors: the tool operates in two distinct modes, provides error diagnostics with fixes in static mode, and sends real test payloads in live mode. However, it lacks details on authentication requirements, rate limits, or response formats, leaving some behavioral aspects unspecified.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is highly concise and well-structured, using only two sentences that front-load key information: the tool's purpose and its two modes. Each sentence earns its place by defining functionality and usage without redundancy, making it efficient and easy to parse.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness4/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

Given the tool's moderate complexity (5 parameters, no output schema, no annotations), the description is largely complete for its purpose. It covers operational modes and parameter contexts effectively. However, it lacks details on output behavior (e.g., what the verification result looks like) and error handling, which would enhance completeness for a debugging tool.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema description coverage is 100%, so the baseline is 3. The description adds significant value by explaining the semantic purpose of parameters: it clarifies that headers, body, and secret are for static verification, while endpointUrl is for live diagnosis. This contextual mapping enhances understanding beyond the schema's technical descriptions, though it doesn't detail parameter interactions or constraints.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description clearly states the tool's purpose with specific verbs ('verify and debug webhook signatures') and distinguishes it from sibling tools by focusing on signature verification rather than webhook management (add_webhook), capability retrieval (get_capabilities), platform listing (list_platforms), or dead-letter queue handling (manage_dlq). It specifies two operational modes, making the purpose highly specific and differentiated.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines5/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The description explicitly defines when to use each mode: static mode for analyzing existing headers/body/secret, and live mode for testing an endpoint URL with real payloads. It provides clear alternatives within the tool itself (mode selection) and implicitly distinguishes from siblings by focusing on verification rather than other webhook operations, offering comprehensive guidance on usage scenarios.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

Install Server

Other Tools

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