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

# Set Up Webhooks

> Create and configure a webhook endpoint to receive Fungies events.

This guide walks you through setting up a webhook endpoint to receive events from Fungies.

## Quick Start

<Steps>
  <Step title="Create endpoint handler">
    Build an HTTP endpoint that accepts POST requests
  </Step>

  <Step title="Register in Dashboard">
    Add your endpoint URL in the Fungies Dashboard
  </Step>

  <Step title="Verify signatures">
    Validate that requests are from Fungies
  </Step>
</Steps>

## 1. Create Your Endpoint Handler

Your webhook endpoint needs to:

* Accept `POST` requests with a JSON body
* Return a `2xx` status code quickly (before any heavy processing)
* Handle the [Event object](/core-resources/event) payload

<Tip>
  Return your `2xx` response immediately, then process the event asynchronously. This prevents timeouts on long-running operations.
</Tip>

### Basic Handler Example

```typescript theme={null}
// This example uses Express to receive webhooks
import express from "express";
const app = express();

app.post('/webhook', express.json({type: 'application/json'}), (request, response) => {
  const event = request.body;

  // Handle the event
  switch (event.type) {
    case 'payment_success':
      // handlePaymentSuccess(event.data);
      break;
    case 'payment_refund':
      // handlePaymentRefund(event.data);
      break;
    // ... handle other event types
    default:
      console.log(`Unhandled event type ${event.type}`);
  }

  response.json({success: true});
});

app.listen(8081, () => console.log('Running on port 8081'));
```

## 2. Register in the Fungies Dashboard

Once your endpoint is ready (or you're using a tool like [ngrok](https://ngrok.com) for local development):

1. Go to [Fungies Dashboard](https://app.fungies.io) → **Developers** → **Webhooks**
2. Click **Create a webhook**
3. Configure your webhook:

| Field      | Description                                         |
| ---------- | --------------------------------------------------- |
| **URL**    | Your endpoint URL (must be HTTPS in production)     |
| **Secret** | A random string to sign events (save this securely) |
| **Events** | Select which event types to receive                 |

4. Click **Save**

<Info>
  You can create multiple webhooks for different event types, or use a single endpoint that handles all events.
</Info>

<Note>
  For subscription products, include **`payment_success`** if your handler fulfills orders. `subscription_created` alone is not enough — see [Subscription checkouts and fulfillment](/developers/webhooks/overview#subscription-checkouts-and-fulfillment).
</Note>

## 3. Verify Webhook Signatures

<Warning>
  Never skip signature verification in production. Without it, anyone could send fake events to your endpoint.
</Warning>

Fungies signs every webhook payload using HMAC-SHA256 with your webhook secret. The signature is included in the `x-fngs-signature` header:

```
x-fngs-signature: sha256_6808ed5be1262b60818359fa586145810d0793e8a677f1326520d3844e21b640
```

### How to Verify

1. Get the raw request body (before any JSON parsing)
2. Compute HMAC-SHA256 using your webhook secret
3. Compare with the signature in the header

### Verification Function

```typescript theme={null}
// This example shows standalone function to verify webhook event signature
import crypto from "crypto";

const verifyWebhookEventSignature = (payload: Buffer, signature: string, secret: string) => {
    const hmac = crypto.createHmac("sha256", secret);
    return `sha256_${hmac.update(payload.toString()).digest("hex")}` === signature;
};
```

### Express Middleware Example

```typescript theme={null}
// This example uses Express to receive webhooks
import express, { Response, Request } from "express";
import crypto from "crypto";

const WEBHOOK_SECRET = "<YOURE_SECRET_HERE>";

const app = express();

const verifyWebhookEventSignature = (request: Request, response: Response, buf: Buffer, encoding: string) => {
    const signature = request.header('x-fngs-signature');
    const hmac = crypto.createHmac("sha256", WEBHOOK_SECRET);
    const calcSignature = `sha256_${hmac.update(buf.toString(encoding)).digest("hex")}`;
    request["signature_match"] = (calcSignature === signature);
};

app.post('/webhook', express.json({
  type: 'application/json',
  verify: verifyWebhookEventSignature
}), (request, response) => {
  if(!request["signature_match"]) {
    return response.status(403).send('signature mismatch');
  }

  // Do something with the event
  // ...
});

app.listen(8081, () => console.log('Running on port 8081'));
```

## Common Patterns

### Idempotent Event Handling

Since webhooks guarantee at-least-once delivery, your handler might receive the same event multiple times. Use the event ID to prevent duplicate processing:

```javascript theme={null}
app.post('/webhook', async (req, res) => {
  const event = req.body;
  
  // Check if we've already processed this event
  const processed = await db.events.findById(event.id);
  if (processed) {
    return res.status(200).send('Already processed');
  }
  
  // Process the event
  await handleEvent(event);
  
  // Mark as processed
  await db.events.create({ id: event.id, processedAt: new Date() });
  
  res.status(200).send('OK');
});
```

### Async Processing with Queues

For complex operations, queue events for background processing:

```javascript theme={null}
app.post('/webhook', async (req, res) => {
  // Respond immediately
  res.status(200).send('Received');
  
  // Queue for async processing
  await queue.add('process-webhook', req.body);
});
```

## Troubleshooting

| Issue                | Solution                                                     |
| -------------------- | ------------------------------------------------------------ |
| Not receiving events | Check your endpoint is publicly accessible and returns `2xx` |
| Signature mismatch   | Ensure you're using the raw body, not parsed JSON            |
| Timeout errors       | Return `2xx` faster, process events asynchronously           |
| Duplicate events     | Implement idempotent handling using event IDs                |

## Next Steps

<Card title="Test Your Webhooks" icon="flask" href="/developers/webhooks/test">
  Learn how to test webhooks locally before deploying to production
</Card>
