Webhooks
Receive real-time subscription events at your endpoint.
Overview
Slootly sends webhook events to your endpoint when subscription state changes. Events are signed with HMAC-SHA256 for verification. Failed deliveries are retried with exponential backoff.
Setup
Register a webhook endpoint in the Slootly dashboard. You receive a webhook secret (whsec_...) used to verify event signatures.
import express from 'express';
import { Slootly } from '@slootly/sdk';
const app = express();
const gate = new Slootly('sk_live_...');
// Use raw body for signature verification
app.post(
'/webhooks/slootly',
express.raw({ type: 'application/json' }),
(req, res) => {
const event = gate.verifyWebhook(
req.body.toString(),
req.headers['slootly-signature'] as string,
'whsec_your_webhook_secret'
);
switch (event.type) {
case 'subscription.created':
console.log('New subscriber:', event.data.subscriberId);
console.log('Plan:', event.data.plan?.slug);
break;
case 'subscription.renewed':
console.log('Renewed:', event.data.subscriberId);
break;
case 'subscription.canceled':
console.log('Canceled:', event.data.subscriberId);
break;
case 'payment.failed':
console.log('Payment failed:', event.data.subscriberId);
break;
}
res.json({ received: true });
}
);Event Types
| Event Type | Description | When It Fires |
|---|---|---|
subscription.created | New subscription created | After successful first payment |
subscription.renewed | Subscription renewed | After successful recurring payment |
subscription.updated | Subscription changed | Plan change, metadata update |
subscription.canceled | Subscription canceled | User or API cancels subscription |
payment.failed | Payment failed | Card declined, insufficient funds |
test.ping | Test event | When you click 'Send test event' in dashboard |
Payload Schema
{
"id": "evt_abc123def456",
"type": "subscription.created",
"created_at": "2026-03-19T12:00:00Z",
"project_id": "proj_xyz789",
"data": {
"subscriber_id": "sub_abc123",
"subscription_id": "subs_def456",
"plan": {
"id": "plan_abc123",
"name": "Pro",
"slug": "pro"
},
"status": "active",
"platform": "telegram",
"platform_user_id": "123456789",
"current_period_start": "2026-03-19T00:00:00Z",
"current_period_end": "2026-04-19T00:00:00Z",
"cancel_at_period_end": false,
"metadata": {}
}
}Signature Verification
Every webhook includes a Slootly-Signature header containing a timestamp and HMAC-SHA256 signature. The SDK handles verification automatically, but here is the manual process:
// Slootly-Signature header format:
// t=1679000000,v1=hmac_sha256_hex_signature
// Verification steps:
// 1. Extract timestamp (t) and signature (v1) from header
// 2. Construct signed payload: `${timestamp}.${rawBody}`
// 3. Compute HMAC-SHA256 with your webhook secret
// 4. Compare computed signature with v1 value
// 5. Verify timestamp is within 5 minutes of current timeWarning
Always verify webhook signatures before processing events. Unverified webhooks could be spoofed by attackers. The SDK methods verifyWebhook() and verify_webhook() handle this automatically.
Retry Behavior
If your endpoint returns a non-2xx status code or times out, Slootly retries the event with exponential backoff:
| Attempt | Delay | Total Time Elapsed |
|---|---|---|
1st retry | 30 seconds | 30 seconds |
2nd retry | 2 minutes | 2.5 minutes |
3rd retry | 10 minutes | 12.5 minutes |
4th retry | 1 hour | 1 hour 12.5 min |
5th retry (final) | 4 hours | 5 hours 12.5 min |
After 5 failed attempts, the event is marked as failed. You can manually retry failed events from the dashboard.
Best Practices
- Return 200 quickly. Process events asynchronously. Return a 200 response immediately and handle business logic in a background job.
- Handle duplicates. Use the event
idfield for idempotency. The same event may be delivered more than once during retries. - Verify signatures. Always use the SDK verification methods. Never skip signature verification.
- Use raw body. Signature verification requires the raw request body, not a parsed JSON object. Configure your framework accordingly.