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

๐Ÿ”” 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

Real-time Updates

Receive instant notifications when events occur

Reduced API Calls

Eliminate polling and reduce API usage

Event Reliability

Automatic retries ensure delivery

๐Ÿš€ Quick Start

1

Create Webhook Endpoint

Set up an HTTPS endpoint on your server to receive webhook events:
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');
});
2

Register Webhook

Register your endpoint with StateSet:
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"
  }'
3

Verify Signatures

Verify webhook signatures to ensure authenticity:
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)
  );
}

๐Ÿ“‹ Event Types

Payment Events

Stablecoin Events

Order Events

Invoice Events

๐Ÿ” Webhook Security

Signature Verification

All webhook requests include a signature in the X-StateSet-Signature header. Always verify this signature:
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');
  }
});

Best Practices

๐Ÿ”ง Webhook Management

Create a Webhook

POST /v1/webhooks
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"
    }
  }'

List Webhooks

GET /v1/webhooks
curl https://api.stateset.com/v1/webhooks \
  -H "Authorization: Bearer sk_test_..."

Update a Webhook

PUT /v1/webhooks/{webhook_id}
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
  }'

Delete a Webhook

DELETE /v1/webhooks/{webhook_id}
curl -X DELETE https://api.stateset.com/v1/webhooks/hook_abc123 \
  -H "Authorization: Bearer sk_test_..."

Test a Webhook

Send a test event to verify your endpoint is working:
POST /v1/webhooks/{webhook_id}/test
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"
  }'

๐Ÿ“Š Webhook Event Structure

All webhook events follow this structure:
{
  "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

FieldDescription
idUnique identifier for the event
objectAlways โ€œeventโ€
typeThe type of event (e.g., โ€œpayment.succeededโ€)
createdUnix timestamp of event creation
dataEvent-specific data object
requestDetails about the API request that triggered the event
pending_webhooksNumber of webhooks yet to be delivered
api_versionAPI version used for this event

๐Ÿ”„ Retry Logic

StateSet automatically retries failed webhook deliveries with exponential backoff:
AttemptDelayTotal Time
1Immediate0 seconds
210 seconds10 seconds
31 minute1.2 minutes
410 minutes11.2 minutes
51 hour1.2 hours
63 hours4.2 hours
712 hours16.2 hours
After 7 attempts over ~16 hours, the webhook is marked as failed.

Handling Failures

Monitor webhook failures through the dashboard or API:
// 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:
# 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:
// 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:
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:
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:
const webhook = await stateset.webhooks.create({
  url: 'https://your-app.com/webhook',
  events: ['*'], // All events
  debug: true // Verbose logging
});

๐Ÿšจ Common Issues

๐ŸŒ 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
IP addresses are subject to change. Subscribe to our changelog for updates.

๐Ÿ“š Next Steps