> ## Documentation Index
> Fetch the complete documentation index at: https://docs.riseworks.io/llms.txt
> Use this file to discover all available pages before exploring further.

# Testing Webhooks

> How to test your webhook endpoints during development

Testing webhooks can be challenging since they're triggered by external events. This guide provides comprehensive strategies for testing your webhook implementations during development, staging, and production phases.

## Testing strategies overview

Effective webhook testing involves multiple approaches:

<CardGroup cols={2}>
  <Card title="Local development" icon="laptop">
    Test on your development machine with tunneling tools
  </Card>

  <Card title="Staging environment" icon="server">
    Test in production-like environment before going live
  </Card>

  <Card title="Production monitoring" icon="chart-line">
    Verify real-world performance with actual events
  </Card>

  <Card title="Automated testing" icon="robot">
    Include webhooks in your continuous integration pipeline
  </Card>
</CardGroup>

## Local development testing

<Info>
  **SDK Installation**: Install the Rise SDK for secure webhook testing: `npm install @riseworks/riseworks-sdk` (or `yarn add @riseworks/riseworks-sdk` / `pnpm add @riseworks/riseworks-sdk`)
</Info>

### Setting up a local webhook endpoint

First, create a simple webhook endpoint for testing:

<CodeGroup>
  ```javascript test-webhook.js theme={null}
  import express from 'express';
  import { WebhookValidator } from '@riseworks/riseworks-sdk';

  const app = express();
  app.use(express.raw({ type: 'application/json' }));

  // Create validator with test secret
  const validator = new WebhookValidator({
    secret: process.env.RISE_WEBHOOK_SECRET || 'test-secret',
    tolerance: 600 // 10 minutes
  });

  app.post('/webhook', async (req, res) => {
    const signature = req.headers['x-rise-signature'];

    console.log('📨 Webhook received:');
    console.log('Headers:', req.headers);
    console.log('Body:', req.body.toString());

    try {
      // Verify signature and get validated event
      const event = validator.validateEvent(req.body, signature);
      console.log('Success: Signature valid');

      console.log('Event details:');
      console.log('- Type:', event.event_type);
      console.log('- ID:', event.id);
      console.log('- Created:', new Date(event.created * 1000));

      res.status(200).json({ received: true });
    } catch (error) {
      console.error('Error: Error processing webhook:', error.message);
      res.status(400).send('Error processing webhook');
    }
  });

  const port = process.env.PORT || 3000;
  app.listen(port, () => {
    console.log(`Webhook test server running on port ${port}`);
    console.log(`Webhook URL: http://localhost:${port}/webhook`);
  });
  ```

  ```python test_webhook.py theme={null}
  from flask import Flask, request, jsonify
  from riseworks import WebhookValidator
  import os
  from datetime import datetime

  app = Flask(__name__)

  # Create validator with test secret
  validator = WebhookValidator(
      secret=os.environ.get('RISE_WEBHOOK_SECRET', 'test-secret'),
      tolerance=600  # 10 minutes
  )

  @app.route('/webhook', methods=['POST'])
  def handle_webhook():
      signature = request.headers.get('X-Rise-Signature')

      print('📨 Webhook received:')
      print('Headers:', dict(request.headers))
      print('Body:', request.get_data().decode())

      try:
          # Verify signature and get validated event
          event = validator.validateEvent(request.get_data(), signature)
          print('Success: Signature valid')

          print('Event details:')
          print(f'- Type: {event["event_type"]}')
          print(f'- ID: {event["id"]}')
          print(f'- Created: {datetime.fromtimestamp(event["created"])}')

          return jsonify({'received': True}), 200
      except Exception as error:
          print(f'Error: Error processing webhook: {error}')
          return 'Error processing webhook', 400

  if __name__ == '__main__':
      port = int(os.environ.get('PORT', 3000))
      print(f'Webhook test server running on port {port}')
      print(f'Webhook URL: http://localhost:{port}/webhook')
      app.run(port=port, debug=True)
  ```
</CodeGroup>

### Making your local endpoint accessible

Use tunneling tools to expose your local server to the internet:

<Tabs>
  <Tab title="ngrok (Recommended)">
    **Installation and usage:**

    ```bash theme={null}
    # Install ngrok
    npm install -g ngrok

    # Start your webhook server
    node test-webhook.js

    # In another terminal, expose it
    ngrok http 3000
    ```

    **Benefits:**

    * Reliable and stable tunnels
    * HTTPS support included
    * Custom subdomain options (paid plans)
    * Web interface for monitoring requests

    **Example output:**

    ```
    Forwarding  https://abc123.ngrok.io -> http://localhost:3000
    ```
  </Tab>

  <Tab title="localtunnel">
    **Installation and usage:**

    ```bash theme={null}
    # Install localtunnel
    npm install -g localtunnel

    # Start your webhook server
    node test-webhook.js

    # In another terminal, create tunnel
    lt --port 3000 --subdomain my-webhook-test
    ```

    **Benefits:**

    * Free and open source
    * Custom subdomain support
    * Simple setup

    **Note:** Less reliable than ngrok for production testing
  </Tab>

  <Tab title="Cloudflare Tunnel">
    **Installation and usage:**

    ```bash theme={null}
    # Install cloudflared
    # Download from https://developers.cloudflare.com/cloudflare-one/connections/connect-apps/install-and-setup/

    # Start tunnel
    cloudflared tunnel --url http://localhost:3000
    ```

    **Benefits:**

    * Built-in DDoS protection
    * Global CDN
    * No bandwidth limits

    **Requirements:** Cloudflare account recommended for custom domains
  </Tab>
</Tabs>

## Using Rise's test webhook feature

The Rise app includes a built-in webhook testing tool:

<Steps>
  <Step title="Access test feature">
    Navigate to your webhook details page in the Rise app and click the **"Test Webhook"** button
  </Step>

  <Step title="Select event type">
    Choose an event type to simulate from the dropdown list (e.g., `payment.sent`, `payment.group.created`)
  </Step>

  <Step title="Choose version (optional)">
    If multiple event versions are available, select the version you want to test
  </Step>

  <Step title="Send test event">
    Click **"Send Test Event"** to deliver a test payload to your endpoint
  </Step>

  <Step title="Verify results">
    Check both the Rise app interface and your server logs to confirm the test was successful
  </Step>
</Steps>

### Interpreting test results

The test interface shows:

<CardGroup cols={2}>
  <Card title="Response status" icon="circle-check">
    HTTP status code returned by your endpoint (should be 200-299)
  </Card>

  <Card title="Response time" icon="stopwatch">
    How long your endpoint took to respond (should be under 10 seconds)
  </Card>

  <Card title="Response body" icon="file-lines">
    What your endpoint returned (helpful for debugging)
  </Card>

  <Card title="Error details" icon="triangle-exclamation">
    Any connection or processing errors that occurred
  </Card>
</CardGroup>

## Testing checklist

Before deploying your webhook implementation, verify all these aspects:

### Functionality tests

<AccordionGroup>
  <Accordion title="Basic functionality" icon="check">
    * Success: Webhook receives and processes events correctly
    * Success: Returns 200 status code within 10 seconds
    * Success: Handles all subscribed event types properly
    * Success: Logs events appropriately for debugging
    * Success: Processes business logic correctly
  </Accordion>

  <Accordion title="Security verification" icon="shield">
    * Success: Signature verification works for valid signatures
    * Success: Invalid signatures are properly rejected with 400 status
    * Success: Timestamp validation prevents replay attacks
    * Success: HTTPS is recommended for production environments
    * Success: Webhook secrets are stored securely
  </Accordion>

  <Accordion title="Error handling" icon="triangle-exclamation">
    * Success: Unknown event types are handled gracefully
    * Success: Malformed payloads don't crash the application
    * Success: Network timeouts are handled appropriately
    * Success: Database errors don't fail webhook processing
    * Success: External API failures are handled gracefully
  </Accordion>
</AccordionGroup>

### Idempotency testing

Test that your webhook can safely handle duplicate events using the `idempotency_key` field:

```javascript theme={null}
// Test idempotency with duplicate events
async function testIdempotency() {
  const testEvent = {
    id: 'we-test123',
    object: 'event',
    created: Math.floor(Date.now() / 1000),
    event_type: 'payment.sent',
          event_version: '1.0',
    request_id: 'req_test789',
    idempotency_key: 'a1b2c3d4-e5f6-7890-abcd-ef1234567890', // UUID for duplicate detection
    payment: {
      nanoid: 'pa-test123',
      amount: '1000000',
      currency: 'USD',
      // ... other fields
    }
  };

  // Send the same event multiple times
  for (let i = 0; i < 3; i++) {
    const response = await fetch('http://localhost:3000/webhook', {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json',
        'X-Rise-Signature': generateTestSignature(testEvent)
      },
      body: JSON.stringify(testEvent)
    });

    console.log(`Attempt ${i + 1}: ${response.status}`);
  }

  // Verify only one payment was processed using idempotency_key
  const processedEvents = await database.count('processed_webhooks', {
    idempotency_key: 'a1b2c3d4-e5f6-7890-abcd-ef1234567890'
  });
  console.log(`Event processed ${processedEvents} times (should be 1)`);
}

// Example idempotency handler implementation
function processWebhook(event) {
  const idempotencyKey = event.idempotency_key;

  // Check if already processed using idempotency_key
  const existingRecord = database.findByIdempotencyKey(idempotencyKey);
  if (existingRecord) {
    console.log(`Event ${idempotencyKey} already processed, skipping`);
    return { processed: false, status: "duplicate" };
  }

  // Process the event
  database.saveEvent({
    idempotency_key: idempotencyKey,
    event_type: event.event_type,
    processed_at: new Date(),
    event_data: event
  });

  // Trigger business logic
  handleBusinessLogic(event);

  return { processed: true, status: "success" };
}
```

## Monitoring test webhook deliveries

Once your webhook is active, track its performance in the Rise app:

<Steps>
  <Step title="Navigate to webhook details">
    Go to your webhook details page in the Rise app
  </Step>

  <Step title="Access delivery history">
    Click on the **"Delivery History"** tab to see all webhook deliveries
  </Step>

  <Step title="Monitor key metrics">
    Track delivery success rates, response times, and error patterns
  </Step>

  <Step title="Set up alerts">
    Configure monitoring alerts for high failure rates or slow response times
  </Step>
</Steps>

### Key metrics to track

<CardGroup cols={3}>
  <Card title="Success rate" icon="percent">
    **Target**: >99%

    Percentage of successful first-attempt deliveries
  </Card>

  <Card title="Response time" icon="clock">
    **Target**: \<2 seconds

    Average time for your endpoint to respond
  </Card>

  <Card title="Error rate" icon="circle-exclamation">
    **Target**: \<1%

    Percentage of deliveries that ultimately fail
  </Card>
</CardGroup>

The delivery history shows:

* **Successful deliveries** - Events delivered with 200 response
* **Failed deliveries** - Events that couldn't be delivered
* **Pending retries** - Events waiting to be retried
* **Response details** - HTTP status codes and response times

## What's next?

Now that you can test your webhooks effectively:

<CardGroup cols={2}>
  <Card title="Monitoring Guide" icon="chart-line" href="/webhooks/operations/monitoring">
    Learn how to track webhook performance in production
  </Card>

  <Card title="Troubleshooting" icon="wrench" href="/webhooks/operations/troubleshooting">
    Advanced troubleshooting techniques for production issues
  </Card>

  <Card title="Delivery Optimization" icon="rocket" href="/webhooks/operations/delivery">
    Review delivery mechanics and performance optimization
  </Card>

  <Card title="Security Best Practices" icon="shield" href="/webhooks/getting-started/security">
    Ensure your webhook implementation is secure
  </Card>
</CardGroup>

<Tip>
  **Testing tip**: Test early, test often, and test everything. A well-tested webhook implementation prevents production surprises and ensures a reliable integration experience.
</Tip>
