Skip to main content
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

Triggered when a new payment is initiated
{
  "id": "evt_1a2b3c4d",
  "type": "payment.created",
  "created": 1640995200,
  "data": {
    "payment_id": "pay_xyz123",
    "amount": "100.00",
    "currency": "ssusd",
    "status": "pending"
  }
}
Triggered when a payment is successfully completed
{
  "id": "evt_2b3c4d5e",
  "type": "payment.succeeded",
  "created": 1640995260,
  "data": {
    "payment_id": "pay_xyz123",
    "amount": "100.00",
    "currency": "ssusd",
    "status": "succeeded",
    "transaction_hash": "0xabc..."
  }
}
Triggered when a payment fails
{
  "id": "evt_3c4d5e6f",
  "type": "payment.failed",
  "created": 1640995320,
  "data": {
    "payment_id": "pay_xyz123",
    "amount": "100.00",
    "currency": "ssusd",
    "status": "failed",
    "failure_reason": "insufficient_funds"
  }
}

Stablecoin Events

Triggered when new ssUSD is minted
{
  "id": "evt_4d5e6f7g",
  "type": "stablecoin.issued",
  "created": 1640995400,
  "data": {
    "issuance_id": "iss_abc123",
    "amount": "50000.00",
    "recipient": "stateset1...",
    "total_supply": "125000000.00"
  }
}
Triggered when ssUSD is burned for USD
{
  "id": "evt_5e6f7g8h",
  "type": "stablecoin.redeemed",
  "created": 1640995500,
  "data": {
    "redemption_id": "red_def456",
    "amount": "10000.00",
    "bank_account": "**** 1234",
    "status": "processing"
  }
}
Triggered when ssUSD is transferred
{
  "id": "evt_6f7g8h9i",
  "type": "stablecoin.transferred",
  "created": 1640995600,
  "data": {
    "transfer_id": "xfr_ghi789",
    "from": "stateset1abc...",
    "to": "stateset1xyz...",
    "amount": "500.00"
  }
}

Order Events

Triggered when a new order is created
Triggered when an order is paid
Triggered when an order is marked as fulfilled
Triggered when an order is cancelled

Invoice Events

Triggered when a new invoice is created
Triggered when an invoice is paid in full
Triggered when a partial payment is made
Triggered when an invoice becomes overdue

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

Never process webhook events without verifying the signature. This prevents attackers from sending fake events.
Return a 200 status code as soon as possible. Process events asynchronously if needed:
app.post('/webhook', async (req, res) => {
  // Acknowledge receipt immediately
  res.status(200).send('OK');
  
  // Process asynchronously
  processEventAsync(req.body);
});
Webhooks may be sent multiple times. Use the event ID to handle duplicates:
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...
}
Your endpoint should handle retries gracefully. StateSet will retry failed webhooks with exponential backoff.

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

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
Common causes:
  • Using wrong webhook secret
  • Not using raw request body for signature
  • Character encoding issues
  • Clock skew (check server time)
Solutions:
  • Store and check event IDs
  • Use idempotency keys
  • Implement proper deduplication logic
Best practices:
  • Respond with 200 immediately
  • Process events asynchronously
  • Optimize database queries
  • Use message queues for heavy processing

๐ŸŒ 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

โŒ˜I