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

# Webhooks

> Real-time event notifications for your StateSet integration

<Info>
  Webhooks allow your application to receive real-time notifications when events occur in your StateSet account, eliminating the need for polling.
</Info>

## 🔔 Overview

StateSet webhooks are HTTP callbacks that notify your application when specific events occur. Instead of continuously polling our API, you can register webhook endpoints to receive automatic notifications.

### Key Benefits

<CardGroup cols={3}>
  <Card title="Real-time Updates" icon="bolt">
    Receive instant notifications when events occur
  </Card>

  <Card title="Reduced API Calls" icon="chart-line-down">
    Eliminate polling and reduce API usage
  </Card>

  <Card title="Event Reliability" icon="shield-check">
    Automatic retries ensure delivery
  </Card>
</CardGroup>

## 🚀 Quick Start

<Steps>
  <Step title="Create Webhook Endpoint">
    Set up an HTTPS endpoint on your server to receive webhook events:

    ```javascript theme={null}
    app.post('/webhooks/stateset', (req, res) => {
      const event = req.body;
      
      // Process the event
      console.log('Received event:', event.type);
      
      // Return 200 to acknowledge receipt
      res.status(200).send('OK');
    });
    ```
  </Step>

  <Step title="Register Webhook">
    Register your endpoint with StateSet:

    ```bash theme={null}
    curl -X POST https://api.stateset.com/v1/webhooks \
      -H "Authorization: Bearer sk_test_..." \
      -H "Content-Type: application/json" \
      -d '{
        "url": "https://your-app.com/webhooks/stateset",
        "events": ["payment.succeeded", "payment.failed"],
        "description": "Production payment notifications"
      }'
    ```
  </Step>

  <Step title="Verify Signatures">
    Verify webhook signatures to ensure authenticity:

    ```javascript theme={null}
    const crypto = require('crypto');

    function verifyWebhookSignature(payload, signature, secret) {
      const expectedSignature = crypto
        .createHmac('sha256', secret)
        .update(payload)
        .digest('hex');
        
      return crypto.timingSafeEqual(
        Buffer.from(signature),
        Buffer.from(expectedSignature)
      );
    }
    ```
  </Step>
</Steps>

## 📋 Event Types

### Payment Events

<AccordionGroup>
  <Accordion title="payment.created">
    Triggered when a new payment is initiated

    ```json theme={null}
    {
      "id": "evt_1a2b3c4d",
      "type": "payment.created",
      "created": 1640995200,
      "data": {
        "payment_id": "pay_xyz123",
        "amount": "100.00",
        "currency": "ssusd",
        "status": "pending"
      }
    }
    ```
  </Accordion>

  <Accordion title="payment.succeeded">
    Triggered when a payment is successfully completed

    ```json theme={null}
    {
      "id": "evt_2b3c4d5e",
      "type": "payment.succeeded",
      "created": 1640995260,
      "data": {
        "payment_id": "pay_xyz123",
        "amount": "100.00",
        "currency": "ssusd",
        "status": "succeeded",
        "transaction_hash": "0xabc..."
      }
    }
    ```
  </Accordion>

  <Accordion title="payment.failed">
    Triggered when a payment fails

    ```json theme={null}
    {
      "id": "evt_3c4d5e6f",
      "type": "payment.failed",
      "created": 1640995320,
      "data": {
        "payment_id": "pay_xyz123",
        "amount": "100.00",
        "currency": "ssusd",
        "status": "failed",
        "failure_reason": "insufficient_funds"
      }
    }
    ```
  </Accordion>
</AccordionGroup>

### Stablecoin Events

<AccordionGroup>
  <Accordion title="stablecoin.issued">
    Triggered when new ssUSD is minted

    ```json theme={null}
    {
      "id": "evt_4d5e6f7g",
      "type": "stablecoin.issued",
      "created": 1640995400,
      "data": {
        "issuance_id": "iss_abc123",
        "amount": "50000.00",
        "recipient": "stateset1...",
        "total_supply": "125000000.00"
      }
    }
    ```
  </Accordion>

  <Accordion title="stablecoin.redeemed">
    Triggered when ssUSD is burned for USD

    ```json theme={null}
    {
      "id": "evt_5e6f7g8h",
      "type": "stablecoin.redeemed",
      "created": 1640995500,
      "data": {
        "redemption_id": "red_def456",
        "amount": "10000.00",
        "bank_account": "**** 1234",
        "status": "processing"
      }
    }
    ```
  </Accordion>

  <Accordion title="stablecoin.transferred">
    Triggered when ssUSD is transferred

    ```json theme={null}
    {
      "id": "evt_6f7g8h9i",
      "type": "stablecoin.transferred",
      "created": 1640995600,
      "data": {
        "transfer_id": "xfr_ghi789",
        "from": "stateset1abc...",
        "to": "stateset1xyz...",
        "amount": "500.00"
      }
    }
    ```
  </Accordion>
</AccordionGroup>

### Order Events

<AccordionGroup>
  <Accordion title="order.created">
    Triggered when a new order is created
  </Accordion>

  <Accordion title="order.paid">
    Triggered when an order is paid
  </Accordion>

  <Accordion title="order.fulfilled">
    Triggered when an order is marked as fulfilled
  </Accordion>

  <Accordion title="order.cancelled">
    Triggered when an order is cancelled
  </Accordion>
</AccordionGroup>

### Invoice Events

<AccordionGroup>
  <Accordion title="invoice.created">
    Triggered when a new invoice is created
  </Accordion>

  <Accordion title="invoice.paid">
    Triggered when an invoice is paid in full
  </Accordion>

  <Accordion title="invoice.partially_paid">
    Triggered when a partial payment is made
  </Accordion>

  <Accordion title="invoice.overdue">
    Triggered when an invoice becomes overdue
  </Accordion>
</AccordionGroup>

## 🔐 Webhook Security

### Signature Verification

All webhook requests include a signature in the `X-StateSet-Signature` header. Always verify this signature:

<CodeGroup>
  ```javascript Node.js theme={null}
  const crypto = require('crypto');

  function verifyWebhook(req, secret) {
    const signature = req.headers['x-stateset-signature'];
    const timestamp = req.headers['x-stateset-timestamp'];
    const payload = JSON.stringify(req.body);
    
    // Prevent replay attacks
    const currentTime = Math.floor(Date.now() / 1000);
    if (currentTime - parseInt(timestamp) > 300) { // 5 minutes
      throw new Error('Webhook timestamp too old');
    }
    
    // Verify signature
    const signedPayload = `${timestamp}.${payload}`;
    const expectedSignature = crypto
      .createHmac('sha256', secret)
      .update(signedPayload)
      .digest('hex');
      
    if (signature !== expectedSignature) {
      throw new Error('Invalid webhook signature');
    }
    
    return JSON.parse(payload);
  }

  // Express middleware
  app.post('/webhook', express.raw({type: 'application/json'}), (req, res) => {
    try {
      const event = verifyWebhook(req, process.env.WEBHOOK_SECRET);
      
      // Process event
      handleWebhookEvent(event);
      
      res.status(200).send('OK');
    } catch (err) {
      console.error('Webhook error:', err.message);
      res.status(400).send('Webhook Error');
    }
  });
  ```

  ```python Python theme={null}
  import hmac
  import hashlib
  import json
  import time

  def verify_webhook(payload, signature, timestamp, secret):
      # Prevent replay attacks
      current_time = int(time.time())
      if current_time - int(timestamp) > 300:  # 5 minutes
          raise ValueError('Webhook timestamp too old')
      
      # Verify signature
      signed_payload = f"{timestamp}.{payload}"
      expected_signature = hmac.new(
          secret.encode('utf-8'),
          signed_payload.encode('utf-8'),
          hashlib.sha256
      ).hexdigest()
      
      if not hmac.compare_digest(signature, expected_signature):
          raise ValueError('Invalid webhook signature')
      
      return json.loads(payload)

  # Flask example
  from flask import Flask, request, abort

  app = Flask(__name__)

  @app.route('/webhook', methods=['POST'])
  def handle_webhook():
      signature = request.headers.get('X-StateSet-Signature')
      timestamp = request.headers.get('X-StateSet-Timestamp')
      
      try:
          event = verify_webhook(
              request.data.decode('utf-8'),
              signature,
              timestamp,
              os.getenv('WEBHOOK_SECRET')
          )
          
          # Process event
          process_webhook_event(event)
          
          return 'OK', 200
      except Exception as e:
          print(f'Webhook error: {e}')
          abort(400)
  ```

  ```go Go theme={null}
  package main

  import (
      "crypto/hmac"
      "crypto/sha256"
      "encoding/hex"
      "encoding/json"
      "fmt"
      "io/ioutil"
      "net/http"
      "strconv"
      "time"
  )

  func verifyWebhook(payload []byte, signature, timestamp, secret string) (map[string]interface{}, error) {
      // Prevent replay attacks
      ts, _ := strconv.ParseInt(timestamp, 10, 64)
      if time.Now().Unix()-ts > 300 { // 5 minutes
          return nil, fmt.Errorf("webhook timestamp too old")
      }
      
      // Verify signature
      signedPayload := fmt.Sprintf("%s.%s", timestamp, string(payload))
      h := hmac.New(sha256.New, []byte(secret))
      h.Write([]byte(signedPayload))
      expectedSignature := hex.EncodeToString(h.Sum(nil))
      
      if !hmac.Equal([]byte(signature), []byte(expectedSignature)) {
          return nil, fmt.Errorf("invalid webhook signature")
      }
      
      var event map[string]interface{}
      err := json.Unmarshal(payload, &event)
      return event, err
  }

  func webhookHandler(w http.ResponseWriter, r *http.Request) {
      signature := r.Header.Get("X-StateSet-Signature")
      timestamp := r.Header.Get("X-StateSet-Timestamp")
      
      body, _ := ioutil.ReadAll(r.Body)
      
      event, err := verifyWebhook(body, signature, timestamp, os.Getenv("WEBHOOK_SECRET"))
      if err != nil {
          http.Error(w, "Invalid webhook", http.StatusBadRequest)
          return
      }
      
      // Process event
      processWebhookEvent(event)
      
      w.WriteHeader(http.StatusOK)
      w.Write([]byte("OK"))
  }
  ```
</CodeGroup>

### Best Practices

<AccordionGroup>
  <Accordion title="Always Verify Signatures">
    Never process webhook events without verifying the signature. This prevents attackers from sending fake events.
  </Accordion>

  <Accordion title="Respond Quickly">
    Return a 200 status code as soon as possible. Process events asynchronously if needed:

    ```javascript theme={null}
    app.post('/webhook', async (req, res) => {
      // Acknowledge receipt immediately
      res.status(200).send('OK');
      
      // Process asynchronously
      processEventAsync(req.body);
    });
    ```
  </Accordion>

  <Accordion title="Handle Duplicates">
    Webhooks may be sent multiple times. Use the event ID to handle duplicates:

    ```javascript theme={null}
    const processedEvents = new Set();

    function handleEvent(event) {
      if (processedEvents.has(event.id)) {
        console.log('Duplicate event:', event.id);
        return;
      }
      
      processedEvents.add(event.id);
      // Process event...
    }
    ```
  </Accordion>

  <Accordion title="Implement Retry Logic">
    Your endpoint should handle retries gracefully. StateSet will retry failed webhooks with exponential backoff.
  </Accordion>
</AccordionGroup>

## 🔧 Webhook Management

### Create a Webhook

```bash theme={null}
POST /v1/webhooks
```

<CodeGroup>
  ```bash cURL theme={null}
  curl -X POST https://api.stateset.com/v1/webhooks \
    -H "Authorization: Bearer sk_test_..." \
    -H "Content-Type: application/json" \
    -d '{
      "url": "https://your-app.com/webhooks/stateset",
      "events": ["payment.succeeded", "payment.failed"],
      "description": "Payment notifications",
      "metadata": {
        "environment": "production"
      }
    }'
  ```

  ```javascript Node.js theme={null}
  const webhook = await stateset.webhooks.create({
    url: 'https://your-app.com/webhooks/stateset',
    events: ['payment.succeeded', 'payment.failed'],
    description: 'Payment notifications',
    metadata: {
      environment: 'production'
    }
  });
  ```
</CodeGroup>

### List Webhooks

```bash theme={null}
GET /v1/webhooks
```

<CodeGroup>
  ```bash cURL theme={null}
  curl https://api.stateset.com/v1/webhooks \
    -H "Authorization: Bearer sk_test_..."
  ```

  ```javascript Node.js theme={null}
  const webhooks = await stateset.webhooks.list({
    limit: 10,
    active: true
  });
  ```
</CodeGroup>

### Update a Webhook

```bash theme={null}
PUT /v1/webhooks/{webhook_id}
```

<CodeGroup>
  ```bash cURL theme={null}
  curl -X PUT https://api.stateset.com/v1/webhooks/hook_abc123 \
    -H "Authorization: Bearer sk_test_..." \
    -H "Content-Type: application/json" \
    -d '{
      "events": ["payment.succeeded", "payment.failed", "order.created"],
      "active": true
    }'
  ```

  ```javascript Node.js theme={null}
  const updated = await stateset.webhooks.update('hook_abc123', {
    events: ['payment.succeeded', 'payment.failed', 'order.created'],
    active: true
  });
  ```
</CodeGroup>

### Delete a Webhook

```bash theme={null}
DELETE /v1/webhooks/{webhook_id}
```

<CodeGroup>
  ```bash cURL theme={null}
  curl -X DELETE https://api.stateset.com/v1/webhooks/hook_abc123 \
    -H "Authorization: Bearer sk_test_..."
  ```

  ```javascript Node.js theme={null}
  await stateset.webhooks.delete('hook_abc123');
  ```
</CodeGroup>

### Test a Webhook

Send a test event to verify your endpoint is working:

```bash theme={null}
POST /v1/webhooks/{webhook_id}/test
```

<CodeGroup>
  ```bash cURL theme={null}
  curl -X POST https://api.stateset.com/v1/webhooks/hook_abc123/test \
    -H "Authorization: Bearer sk_test_..." \
    -H "Content-Type: application/json" \
    -d '{
      "event_type": "payment.succeeded"
    }'
  ```

  ```javascript Node.js theme={null}
  const result = await stateset.webhooks.test('hook_abc123', {
    event_type: 'payment.succeeded'
  });
  ```
</CodeGroup>

## 📊 Webhook Event Structure

All webhook events follow this structure:

```json theme={null}
{
  "id": "evt_1a2b3c4d5e6f",
  "object": "event",
  "type": "payment.succeeded",
  "created": 1640995200,
  "data": {
    // Event-specific data
  },
  "request": {
    "id": "req_xyz789",
    "idempotency_key": "key_123"
  },
  "pending_webhooks": 1,
  "api_version": "2024-01-15"
}
```

### Event Fields

| Field              | Description                                            |
| ------------------ | ------------------------------------------------------ |
| `id`               | Unique identifier for the event                        |
| `object`           | Always "event"                                         |
| `type`             | The type of event (e.g., "payment.succeeded")          |
| `created`          | Unix timestamp of event creation                       |
| `data`             | Event-specific data object                             |
| `request`          | Details about the API request that triggered the event |
| `pending_webhooks` | Number of webhooks yet to be delivered                 |
| `api_version`      | API version used for this event                        |

## 🔄 Retry Logic

StateSet automatically retries failed webhook deliveries with exponential backoff:

| Attempt | Delay      | Total Time   |
| ------- | ---------- | ------------ |
| 1       | Immediate  | 0 seconds    |
| 2       | 10 seconds | 10 seconds   |
| 3       | 1 minute   | 1.2 minutes  |
| 4       | 10 minutes | 11.2 minutes |
| 5       | 1 hour     | 1.2 hours    |
| 6       | 3 hours    | 4.2 hours    |
| 7       | 12 hours   | 16.2 hours   |

After 7 attempts over \~16 hours, the webhook is marked as failed.

### Handling Failures

Monitor webhook failures through the dashboard or API:

```javascript theme={null}
// Get failed webhook attempts
const failures = await stateset.webhooks.failures.list({
  webhook_id: 'hook_abc123',
  limit: 20
});

// Retry a failed webhook
await stateset.webhooks.failures.retry('fail_xyz789');
```

## 🧪 Testing Webhooks

### Local Development

Use ngrok to test webhooks locally:

```bash theme={null}
# Install ngrok
brew install ngrok

# Expose your local server
ngrok http 3000

# Use the ngrok URL for webhooks
# https://abc123.ngrok.io/webhooks/stateset
```

### Webhook Testing Tool

Use our webhook testing tool in the dashboard:

1. Navigate to Webhooks → Testing
2. Select an event type
3. Customize the payload
4. Send test event

### Unit Testing

Mock webhook events in your tests:

```javascript theme={null}
// Jest example
const mockWebhookEvent = {
  id: 'evt_test_123',
  type: 'payment.succeeded',
  created: Date.now() / 1000,
  data: {
    payment_id: 'pay_test_123',
    amount: '100.00',
    currency: 'ssusd'
  }
};

test('handles payment.succeeded webhook', async () => {
  const result = await handleWebhook(mockWebhookEvent);
  expect(result.processed).toBe(true);
});
```

## 📈 Monitoring & Debugging

### Webhook Logs

View detailed logs for all webhook attempts:

```javascript theme={null}
const logs = await stateset.webhooks.logs.list({
  webhook_id: 'hook_abc123',
  start_date: '2024-01-01',
  end_date: '2024-01-31'
});

logs.data.forEach(log => {
  console.log(`${log.created}: ${log.status} - ${log.response_code}`);
});
```

### Metrics

Track webhook performance:

```javascript theme={null}
const metrics = await stateset.webhooks.metrics({
  webhook_id: 'hook_abc123',
  period: 'day'
});

console.log(`Success rate: ${metrics.success_rate}%`);
console.log(`Average latency: ${metrics.avg_latency}ms`);
```

### Debug Mode

Enable debug mode for verbose logging:

```javascript theme={null}
const webhook = await stateset.webhooks.create({
  url: 'https://your-app.com/webhook',
  events: ['*'], // All events
  debug: true // Verbose logging
});
```

## 🚨 Common Issues

<AccordionGroup>
  <Accordion title="Webhook Not Receiving Events">
    **Checklist:**

    * Verify the webhook URL is correct and publicly accessible
    * Check that SSL certificate is valid (HTTPS required)
    * Ensure the webhook is active
    * Verify event types are subscribed
    * Check firewall rules allow StateSet IPs
  </Accordion>

  <Accordion title="Signature Verification Failing">
    **Common causes:**

    * Using wrong webhook secret
    * Not using raw request body for signature
    * Character encoding issues
    * Clock skew (check server time)
  </Accordion>

  <Accordion title="Duplicate Events">
    **Solutions:**

    * Store and check event IDs
    * Use idempotency keys
    * Implement proper deduplication logic
  </Accordion>

  <Accordion title="Timeouts">
    **Best practices:**

    * Respond with 200 immediately
    * Process events asynchronously
    * Optimize database queries
    * Use message queues for heavy processing
  </Accordion>
</AccordionGroup>

## 🌐 IP Allowlist

For enhanced security, allowlist StateSet webhook IPs:

```
Production:
- 34.123.45.67/32
- 35.234.56.78/32
- 36.345.67.89/32

Test Mode:
- 10.123.45.67/32
- 11.234.56.78/32
```

<Note>
  IP addresses are subject to change. Subscribe to our changelog for updates.
</Note>

## 📚 Next Steps

<CardGroup cols={2}>
  <Card title="Event Reference" icon="book" href="/webhooks/events">
    Complete list of all webhook events
  </Card>

  <Card title="Webhook Dashboard" icon="chart-line" href="https://dashboard.stateset.com/webhooks">
    Manage webhooks in the dashboard
  </Card>

  <Card title="Code Examples" icon="code" href="https://github.com/stateset/webhook-examples">
    Example implementations in various languages
  </Card>

  <Card title="API Reference" icon="terminal" href="/api-reference/webhooks">
    Detailed webhook API documentation
  </Card>
</CardGroup>
