Failure Classification#

HookRelay automatically classifies every delivery failure to help you quickly identify and resolve issues. This guide explains each failure type and how to fix them in your Next.js application.

Understanding Failure Classification#

When a webhook delivery fails, HookRelay analyzes the failure and assigns it to a category and specific reason. This classification helps you understand:

  • What went wrong - The specific failure reason
  • Why it failed - The category (timeout, error, rejected, system)
  • How to fix it - Actionable resolution steps

Failure Categories#

Failures are grouped into four high-level categories:

1. Timeout#

The request to your Next.js handler took too long to respond (exceeded 30 seconds).

Common Causes in Next.js:

  • Heavy synchronous processing in the handler
  • Slow database queries without proper indexing
  • Blocking external API calls
  • Large file processing
  • Complex computations

How to Fix:

// ❌ BAD: Long-running operation blocks response
export const POST = withHookRelay(
  async (event) => {
    // This takes 45 seconds - will timeout!
    await processLargeFile(event.payload);
    await sendEmails(event.payload);
    await updateDatabase(event.payload);
  },
  { provider: 'stripe' }
);

// ✅ GOOD: Queue for background processing
export const POST = withHookRelay(
  async (event) => {
    // Queue immediately, return quickly
    await queueJob({
      type: 'process-webhook',
      eventId: event.id,
      payload: event.payload
    });
    // Handler completes in < 1 second
  },
  { provider: 'stripe' }
);

Best Practices:

  • Return a 2xx status code immediately
  • Use background job queues (Vercel Queue, Inngest, etc.)
  • Process asynchronously after acknowledging receipt
  • Optimize database queries with proper indexes
  • Add timeouts to external API calls

2. Error#

Server or connection errors prevented delivery.

Common Causes:

  • Your handler returned a 5xx status code (500, 502, 503, etc.)
  • Unhandled exceptions in your code
  • Database connection failures
  • DNS resolution failures
  • Connection refused (server down or unreachable)
  • SSL/TLS certificate errors

How to Fix:

// ❌ BAD: Unhandled exception causes 500 error
export const POST = withHookRelay(
  async (event) => {
    const data = event.payload.data.object;
    await processPayment(data.id); // Might throw!
  },
  { provider: 'stripe' }
);

// ✅ GOOD: Proper error handling
export const POST = withHookRelay(
  async (event) => {
    try {
      const data = event.payload.data.object;
      await processPayment(data.id);
    } catch (error) {
      // Log the error
      console.error('Payment processing failed:', error);
      
      // Re-throw if it's retryable
      if (isRetryableError(error)) {
        throw error; // Will trigger retry
      } else {
        // Non-retryable - log and continue
        await logError(event.id, error);
      }
    }
  },
  { provider: 'stripe' }
);

Troubleshooting Steps:

  1. Check your Next.js application logs (Vercel logs, console, etc.)
  2. Verify your application is running and accessible
  3. Test the endpoint manually with a POST request
  4. Check database connectivity
  5. Verify environment variables are set correctly

3. Rejected#

Your endpoint actively rejected the webhook.

Common Causes:

  • Handler returned a 4xx status code (400, 401, 403, 404, etc.)
  • Authentication/authorization failures
  • Invalid request format
  • Missing required headers or data
  • Rate limiting (429)

How to Fix:

// ❌ BAD: Returning 400 for validation errors
export const POST = withHookRelay(
  async (event) => {
    if (!event.payload.data) {
      return new Response('Invalid payload', { status: 400 });
    }
    // ...
  },
  { provider: 'stripe' }
);

// ✅ GOOD: Handle validation gracefully
export const POST = withHookRelay(
  async (event) => {
    // Validate but don't reject - log and continue
    if (!event.payload.data) {
      console.warn('Missing data in payload:', event.id);
      // Still return success to avoid retries
      return;
    }
    await processEvent(event.payload);
  },
  { provider: 'stripe' }
);

Common 4xx Errors:

  • 400 Bad Request - Usually indicates a bug in your handler
  • 401 Unauthorized - Authentication issue (check HookRelay secret)
  • 403 Forbidden - Authorization issue
  • 404 Not Found - Route doesn’t exist (check your Forward URL)
  • 429 Too Many Requests - Rate limiting (this one IS retried)

Note: Most 4xx errors are NOT retried because they indicate a configuration or code issue that won’t be fixed by retrying.

4. System#

HookRelay system-level issues (rare).

Common Causes:

  • Endpoint was deleted while events were in queue
  • Invalid endpoint configuration
  • Event exceeded maximum retry limit

How to Fix:

  • Verify the endpoint still exists in the dashboard
  • Check endpoint configuration is valid
  • Review retry settings
  • Contact support if the issue persists

Specific Failure Reasons#

Each failure is classified with a specific reason code:

handler_timeout#

Your Next.js handler didn’t respond within 30 seconds.

What it means: HookRelay waited 30 seconds for a response but didn’t receive one.

Common causes:

  • Long-running database operations
  • Slow external API calls
  • Heavy computation
  • No response returned from handler

Fix:

// Process asynchronously
export const POST = withHookRelay(
  async (event) => {
    // Queue immediately
    await queueBackgroundJob(event.payload);
    // Returns in < 1 second
  },
  { provider: 'stripe' }
);

handler_connection_error#

Network connection to your endpoint failed.

What it means: HookRelay couldn’t establish a connection to your Forward URL.

Common causes:

  • DNS resolution failed
  • Connection refused (server down)
  • SSL/TLS handshake failed
  • Network routing issues

Fix:

  • Verify your Forward URL is correct and accessible
  • Ensure your Next.js app is deployed and running
  • Check DNS settings if using a custom domain
  • Verify SSL certificate is valid
  • Test the URL manually: curl -X POST https://your-app.com/api/webhooks/stripe

handler_non_2xx#

Your handler returned a non-2xx HTTP status code.

What it means: Your handler returned 3xx, 4xx, or 5xx instead of 2xx.

Common causes:

  • Unhandled exceptions (500)
  • Validation errors (400)
  • Authentication failures (401)
  • Route not found (404)

Fix:

  • Ensure your handler returns 2xx on success
  • The withHookRelay wrapper does this automatically
  • Handle errors gracefully without returning error status codes
  • Check your handler logic for early returns with error codes

retry_limit_exceeded#

Event failed after maximum retry attempts (5 by default).

What it means: All retry attempts were exhausted and the event is now permanently failed.

Common causes:

  • Persistent issue that wasn’t fixed between retries
  • Configuration problem that requires manual intervention
  • Application bug that needs code changes

Fix:

  1. Review all delivery attempts to understand the failure pattern
  2. Fix the underlying issue in your handler
  3. Use the replay feature to reprocess the event
  4. Monitor new events to ensure the fix works

signature_verification_failed#

Provider webhook signature verification failed.

What it means: The webhook signature from your provider (Stripe, GitHub, etc.) didn’t match, indicating the webhook may be invalid or tampered with.

Common causes:

  • Provider secret is incorrect or outdated
  • Webhook was modified in transit
  • Provider secret was rotated but not updated in HookRelay

Fix:

  • Verify the Provider Secret in your endpoint settings matches your provider’s dashboard
  • Update the secret if it was rotated
  • Check that you’re using the correct secret (not the HookRelay secret)

signature_missing#

Expected webhook signature was missing from the provider.

What it means: You configured a Provider Secret, but the webhook didn’t include a signature header.

Common causes:

  • Provider doesn’t send signatures for this webhook type
  • Signature header name mismatch
  • Provider configuration issue

Fix:

  • Verify your provider actually sends signatures
  • Check provider documentation for signature requirements
  • Consider making the Provider Secret optional if signatures aren’t available

endpoint_not_found#

The endpoint configuration was deleted or not found.

What it means: The endpoint ID in the webhook URL doesn’t exist in HookRelay.

Common causes:

  • Endpoint was deleted
  • Endpoint ID is incorrect in the provider’s webhook URL
  • Endpoint was never created

Fix:

  • Verify the endpoint exists in the dashboard
  • Check the webhook URL in your provider’s settings
  • Recreate the endpoint if it was deleted

invalid_endpoint_config#

Endpoint configuration is invalid or incomplete.

What it means: The endpoint has missing or invalid settings.

Common causes:

  • Forward URL is missing or invalid
  • Forward URL doesn’t use HTTPS
  • Configuration was corrupted

Fix:

  • Check endpoint settings in the dashboard
  • Ensure Forward URL is set and uses HTTPS
  • Update the configuration if needed

Viewing Failures#

Failures are displayed in several places in the dashboard:

1. Event Detail Page#

  • Failure Badge - Color-coded badge next to the event status
  • Failure Reason - Specific reason code (e.g., handler_timeout)
  • Failure Category - High-level category (Timeout, Error, Rejected, System)
  • Hover for Details - Additional context and description

2. Delivery Attempts Table#

  • Each failed attempt shows its specific failure reason
  • View HTTP response status codes
  • See error messages and connection details
  • Track progression of failure types across attempts

3. Events List#

  • Filter by status to find all failed events
  • Quickly identify patterns (e.g., all timeouts)
  • Sort by failure reason to group similar issues

Debugging Workflow#

When investigating failed webhooks, follow this workflow:

Step 1: Identify the Failure#

  1. Go to the Events page
  2. Find the failed event
  3. Click to view details
  4. Note the failure reason and category

Step 2: Review Attempt History#

  1. Scroll to “Delivery Attempts” table
  2. Review each attempt:
    • What was the response status?
    • How long did it take?
    • What was the failure reason?
  3. Look for patterns:
    • Same error every time? → Code/config issue
    • Different errors? → Intermittent issue
    • Timeout then error? → Performance issue

Step 3: Check Your Application#

  1. Review your Next.js application logs:

    # Vercel
    vercel logs
    
    # Or check your logging service
  2. Test your handler manually:

    curl -X POST https://your-app.com/api/webhooks/stripe \
      -H "Content-Type: application/json" \
      -H "X-HookRelay-Event-Id: test" \
      -H "X-HookRelay-Provider: stripe" \
      -H "X-HookRelay-Signature: sha256=..." \
      -d '{"test": "payload"}'
  3. Verify environment variables:

    # Check HOOKRELAY_SECRET is set
    echo $HOOKRELAY_SECRET

Step 4: Fix the Issue#

Based on the failure reason:

  • Timeout → Move processing to background jobs
  • Error → Fix the code bug or infrastructure issue
  • Rejected → Fix validation or authentication
  • System → Check endpoint configuration

Step 5: Test the Fix#

  1. Use the replay feature to test with the same event
  2. Monitor new events to ensure the fix works
  3. Set up alerts for future failures

Common Failure Patterns#

Pattern: All Attempts Timeout#

Symptom: Every attempt shows handler_timeout

Cause: Handler is consistently taking > 30 seconds

Fix: Move processing to background jobs

Pattern: Timeout → Error → Timeout#

Symptom: Alternating timeout and error failures

Cause: Handler is slow and sometimes crashes

Fix: Optimize handler performance and add error handling

Pattern: All Attempts Return 500#

Symptom: Every attempt shows handler_non_2xx with 500 status

Cause: Unhandled exception in your code

Fix: Add try/catch and proper error handling

Pattern: First Attempt Succeeds, Retries Fail#

Symptom: Attempt 1 succeeds, but retries fail

Cause: Idempotency issue - handler processes event twice incorrectly

Fix: Implement proper idempotency checks

Best Practices to Avoid Failures#

  1. Return Quickly - Acknowledge receipt immediately, process later
  2. Handle Errors - Use try/catch to prevent unhandled exceptions
  3. Validate Input - Check payload structure but don’t reject (log instead)
  4. Use Idempotency - Check if event was already processed
  5. Monitor Logs - Set up logging to track handler execution
  6. Test Thoroughly - Use replay to test handler changes
  7. Set Up Alerts - Get notified of failures immediately

Getting Help#

If you’re stuck debugging a failure:

  1. Check this guide for the specific failure reason
  2. Review the Delivery & Retries guide for retry behavior
  3. Check the Next.js Integration guide for handler best practices
  4. Contact support through the dashboard with the event ID