Cadence
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:

  • SUBSCRIBED
  • DID_RENEW

Slack setup:

  1. Open https://api.slack.com/apps and create a Slack app (or choose an existing one).
  2. In your Slack app settings, open Incoming Webhooks and enable it.
  3. Click Add New Webhook to Workspace, then choose the channel to receive notifications.
  4. Copy the generated URL that starts with https://hooks.slack.com/services/.
  5. In Cadence, go to Settings -> Webhooks and paste it into Slack Incoming Webhook URL.
  6. Save settings and trigger a real event to verify delivery.

Event Types

TypeSubtypeTriggered ByDescription
SUBSCRIBEDINITIAL_BUYCheckout callback successFired when a new subscription is created via checkout
DID_RENEWBILLING_RECOVERYiyzico subscription.order.success webhookFired when a subscription successfully renews (includes grace_period in data)
DID_FAIL_TO_RENEWiyzico subscription.order.failure webhookFired when a subscription renewal payment fails (includes grace_period in data)
DID_CHANGE_RENEWAL_STATUSAUTO_RENEW_DISABLEDDELETE /subscribers endpointFired when a subscription is cancelled (auto-renewal disabled)
DID_CHANGE_RENEWAL_PREFUPGRADE / DOWNGRADEPUT /subscribers endpointFired when a subscription plan is upgraded or downgraded
EXPIREDScheduled job (coming soon)Fired when a cancelled subscription reaches its end date

Request Headers

Each webhook request includes these headers:

HeaderDescriptionExample
X-Segno-SignatureHMAC-SHA256 signature prefixed with "sha256="sha256=abc123...
X-Segno-EventThe event type (same as notificationType in payload)SUBSCRIBED
X-Segno-Event-IDStable event ID for idempotency (same across retries)checkout_550e8400-...
X-Segno-Transaction-IDUnique ID per delivery attemptf47ac10b-58cc-4372-...
Content-TypeAlways application/jsonapplication/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 eventId to deduplicate events in case of retries.
  • Process asynchronously: Queue webhook events for background processing to respond quickly.
  • Validate signatures: Always verify the X-Segno-Signature header 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;
}

On this page