API Reference
Webhooks
Receive real-time subscription lifecycle events and verify their signatures.
Outgoing Webhooks
Receive real-time notifications when subscription lifecycle events occur. Configure your webhook URL in Settings → Webhooks to start receiving events.
Slack Notifications
You can also send selected webhook events to a Slack channel by configuring Slack Incoming Webhook URL in Settings → Webhooks.
Supported Slack events:
SUBSCRIBEDDID_RENEW
Slack setup:
- Open https://api.slack.com/apps and create a Slack app (or choose an existing one).
- In your Slack app settings, open Incoming Webhooks and enable it.
- Click Add New Webhook to Workspace, then choose the channel to receive notifications.
- Copy the generated URL that starts with https://hooks.slack.com/services/.
- In Cadence, go to Settings -> Webhooks and paste it into Slack Incoming Webhook URL.
- Save settings and trigger a real event to verify delivery.
Event Types
| Type | Subtype | Triggered By | Description |
|---|---|---|---|
SUBSCRIBED | INITIAL_BUY | Checkout callback success | Fired when a new subscription is created via checkout |
DID_RENEW | BILLING_RECOVERY | iyzico subscription.order.success webhook | Fired when a subscription successfully renews (includes grace_period in data) |
DID_FAIL_TO_RENEW | iyzico subscription.order.failure webhook | Fired when a subscription renewal payment fails (includes grace_period in data) | |
DID_CHANGE_RENEWAL_STATUS | AUTO_RENEW_DISABLED | DELETE /subscribers endpoint | Fired when a subscription is cancelled (auto-renewal disabled) |
DID_CHANGE_RENEWAL_PREF | UPGRADE / DOWNGRADE | PUT /subscribers endpoint | Fired when a subscription plan is upgraded or downgraded |
EXPIRED | Scheduled job (coming soon) | Fired when a cancelled subscription reaches its end date |
Request Headers
Each webhook request includes these headers:
| Header | Description | Example |
|---|---|---|
X-Segno-Signature | HMAC-SHA256 signature prefixed with "sha256=" | sha256=abc123... |
X-Segno-Event | The event type (same as notificationType in payload) | SUBSCRIBED |
X-Segno-Event-ID | Stable event ID for idempotency (same across retries) | checkout_550e8400-... |
X-Segno-Transaction-ID | Unique ID per delivery attempt | f47ac10b-58cc-4372-... |
Content-Type | Always application/json | application/json |
Payload Structure
// Webhook payload structure (v2.0)
interface WebhookPayload {
notificationType: WebhookEventType;
subtype?: WebhookEventSubtype;
eventId: string; // Stable across retries (use for idempotency)
transactionId: string; // Unique per delivery attempt
attempt: number; // 1-indexed attempt number
purchaseDate: number; // Unix timestamp in seconds
signedDate: number; // Unix timestamp in milliseconds
version: string; // API version (currently "2.0")
data: WebhookSubscriptionData;
}
type WebhookEventType =
| 'SUBSCRIBED'
| 'DID_CHANGE_RENEWAL_STATUS'
| 'EXPIRED'
| 'DID_RENEW'
| 'DID_FAIL_TO_RENEW'
| 'DID_CHANGE_RENEWAL_PREF';
type WebhookEventSubtype =
| 'INITIAL_BUY'
| 'RESUBSCRIBE'
| 'AUTO_RENEW_ENABLED'
| 'AUTO_RENEW_DISABLED'
| 'BILLING_RECOVERY'
| 'UPGRADE'
| 'DOWNGRADE';
interface WebhookSubscriptionData {
subscriptionId: string; // Cadence database ID
subscriptionReference: string; // iyzico subscription reference
customerReference: string; // iyzico customer reference
planReference: string; // iyzico plan reference
status: string; // ACTIVE, CANCELLED, PAYMENT_FAILED, etc.
grace_period?: number | null; // Plan grace period in days (DID_RENEW and DID_FAIL_TO_RENEW)
// Timing fields (unix seconds, omitted for older subscriptions)
subscriptionFirstStartDate?: number;
periodStart?: number; // Start of current billing period
periodEnd?: number; // End of current billing period (next renewal date)
customer?: {
id: string; // Cadence customer ID
email: string;
name?: string;
surname?: string;
};
}Example Payload
{
"notificationType": "DID_RENEW",
"subtype": "BILLING_RECOVERY",
"eventId": "checkout_550e8400-e29b-41d4-a716-446655440000",
"transactionId": "f47ac10b-58cc-4372-a567-0e02b2c3d479",
"attempt": 1,
"purchaseDate": 1737452000,
"signedDate": 1737452000000,
"version": "2.0",
"data": {
"subscriptionId": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"subscriptionReference": "sub_iyzicoref123",
"customerReference": "cust_iyzicoref456",
"planReference": "plan_iyzicoref789",
"status": "ACTIVE",
"grace_period": 7,
"subscriptionFirstStartDate": 1737452000,
"periodStart": 1737452000,
"periodEnd": 1740130400,
"customer": {
"id": "b2c3d4e5-f6a7-8901-bcde-f23456789012",
"email": "user@example.com",
"name": "John",
"surname": "Doe"
}
}
}Signature Verification
Always verify the X-Segno-Signature header to ensure webhooks are authentic. The signature is an HMAC-SHA256 hash of the request body using your webhook secret.
Verification Example (Node.js)
const crypto = require('crypto');
// Verify webhook signature
function verifyWebhookSignature(
payload: object,
signature: string,
secret: string
): boolean {
const payloadString = JSON.stringify(payload);
const expected = 'sha256=' + crypto
.createHmac('sha256', secret)
.update(payloadString)
.digest('hex');
return signature === expected;
}
// Example Express.js handler
app.post('/webhooks', express.json(), (req, res) => {
const signature = req.headers['x-segno-signature'];
const eventType = req.headers['x-segno-event'];
const eventId = req.headers['x-segno-event-id'];
const transactionId = req.headers['x-segno-transaction-id'];
if (!verifyWebhookSignature(req.body, signature, process.env.WEBHOOK_SECRET)) {
return res.status(401).json({ error: 'Invalid signature' });
}
// Use eventId for idempotency (same across retries)
// Use transactionId to identify specific delivery attempts
console.log('Received webhook:', eventType, 'eventId:', eventId, 'attempt:', req.body.attempt);
// Process the webhook based on event type
switch (req.body.notificationType) {
case 'SUBSCRIBED':
// Handle new subscription
break;
case 'DID_RENEW':
// Handle successful renewal
break;
case 'DID_FAIL_TO_RENEW':
// Handle failed renewal - maybe send reminder email
break;
case 'DID_CHANGE_RENEWAL_STATUS':
// Handle cancellation
break;
// ... handle other events
}
// Always respond quickly to avoid timeouts
res.json({ status: 'received' });
});Best Practices
- Respond quickly: Return a 200 status within 10 seconds to avoid timeouts and retries.
- Use idempotency: Use
eventIdto deduplicate events in case of retries. - Process asynchronously: Queue webhook events for background processing to respond quickly.
- Validate signatures: Always verify the
X-Segno-Signatureheader before processing. - Handle retries: Webhooks are retried up to 10 times with 60s fixed intervals.
Expected Response
Your endpoint should return a 2xx status code.
// Expected response from your webhook endpoint
interface WebhookResponse {
status: "received" | "ok"; // Any success indicator
message?: string;
}