Subscription Webhooks

Subscription webhooks notify your server when a subscription or subscription invoice changes.

Use these events to keep your own system in sync with subscription access, billing status, failed payments, retries, and cancellations.

Webhook Setup

  • Configure your webhook URL in your business settings.
  • Kyshi sends subscription webhooks to the configured webhook URL using the same webhook delivery system as transaction webhooks.
  • Webhook requests are signed using your API key secret.
  • Your server should verify the signature before trusting the event.

Webhook Payload Format

Subscription webhook payloads follow this structure:

{
  "reference": "INV_xxxxxxxxxxxxx",
  "amount": 5000,
  "event": "invoice.payment_succeeded",
  "status": "success",
  "data": {
    "subscription": {
      "id": "7697cabd-ee1b-435a-9ae3-82b926cc5334",
      "code": "SUB_xxxxxxxxxxxxx",
      "status": "ACTIVE",
      "isActive": true,
      "startDate": "2026-05-01T00:00:00.000Z",
      "previousPaymentDate": "2026-05-01T00:00:00.000Z",
      "nextPaymentDate": "2026-06-01T00:00:00.000Z",
      "currentPeriodStart": "2026-05-01T00:00:00.000Z",
      "currentPeriodEnd": "2026-06-01T00:00:00.000Z",
      "pastDueAt": null,
      "nextRetryAt": null,
      "retryCount": 0,
      "maxRetryCount": 3,
      "gracePeriodDays": 3,
      "cancelledAt": null,
      "cancelReason": null,
      "invoiceLimit": 12,
      "invoicesPaid": 1
    },
    "invoice": {
      "id": "14bbd889-32f4-40dd-92ad-75d35028f874",
      "code": "INV_xxxxxxxxxxxxx",
      "amount": 5000,
      "currency": "NGN",
      "periodStart": "2026-05-01T00:00:00.000Z",
      "periodEnd": "2026-06-01T00:00:00.000Z",
      "dueAt": "2026-05-01T00:00:00.000Z",
      "nextRetryAt": null,
      "retryCount": 0,
      "maxRetryCount": 3,
      "status": "PAYMENT_SUCCEEDED",
      "description": null,
      "transactionId": "8b3dc3e2-10fd-487b-86e2-dacb975110c6"
    }
  },
  "meta": {
    "mode": "live",
    "kyshiEventId": "9adf03c3-7162-4cd9-95c3-4b2f66f7e4b7",
    "kyshiWebhookQueuedAt": "2026-05-01T00:01:10.000Z"
  }
}

 

Subscription Events

EventDescription
subscription.createdA subscription was created.
subscription.activeSubscription became active or recovered after successful payment.
subscription.past_dueSubscription renewal failed and entered past-due state.
subscription.cancelledSubscription was cancelled or failed after retry/grace exhaustion.
subscription.completedSubscription completed all configured billing cycles.
subscription.not_renewingSubscription will not renew after the current paid period.

Invoice Events

EventDescription
invoice.createdA billing invoice was created for a subscription cycle.
invoice.updatedInvoice status or retry information changed.
invoice.payment_succeededSubscription invoice payment succeeded.
invoice.payment_failedSubscription invoice payment failed.

Event Examples

{
  "reference": "SUB_xxxxxxxxxxxxx",
  "amount": 0,
  "event": "subscription.past_due",
  "status": "success",
  "data": {
    "subscription": {
      "id": "7697cabd-ee1b-435a-9ae3-82b926cc5334",
      "code": "SUB_xxxxxxxxxxxxx",
      "status": "PAST_DUE",
      "isActive": false,
      "pastDueAt": "2026-06-01T00:00:00.000Z",
      "nextRetryAt": "2026-06-02T00:00:00.000Z",
      "retryCount": 1,
      "maxRetryCount": 3,
      "gracePeriodDays": 3
    },
    "reason": "Insufficient funds"
  },
  "meta": {
    "mode": "live",
    "kyshiEventId": "evt_xxxxx",
    "kyshiWebhookQueuedAt": "2026-06-01T00:01:00.000Z"
  }
}

 
{
  "reference": "INV_xxxxxxxxxxxxx",
  "amount": 5000,
  "event": "invoice.payment_failed",
  "status": "failed",
  "data": {
    "subscription": {
      "id": "7697cabd-ee1b-435a-9ae3-82b926cc5334",
      "code": "SUB_xxxxxxxxxxxxx",
      "status": "PAST_DUE",
      "isActive": false
    },
    "invoice": {
      "id": "14bbd889-32f4-40dd-92ad-75d35028f874",
      "code": "INV_xxxxxxxxxxxxx",
      "amount": 5000,
      "currency": "NGN",
      "status": "PAYMENT_FAILED",
      "description": "Insufficient funds",
      "retryCount": 1,
      "maxRetryCount": 3,
      "nextRetryAt": "2026-06-02T00:00:00.000Z"
    }
  },
  "meta": {
    "mode": "live",
    "kyshiEventId": "evt_xxxxx",
    "kyshiWebhookQueuedAt": "2026-06-01T00:01:00.000Z"
  }
}

{
  "reference": "INV_xxxxxxxxxxxxx",
  "amount": 5000,
  "event": "invoice.payment_succeeded",
  "status": "success",
  "data": {
    "subscription": {
      "id": "7697cabd-ee1b-435a-9ae3-82b926cc5334",
      "code": "SUB_xxxxxxxxxxxxx",
      "status": "ACTIVE",
      "isActive": true,
      "previousPaymentDate": "2026-06-01T00:00:00.000Z",
      "nextPaymentDate": "2026-07-01T00:00:00.000Z"
    },
    "invoice": {
      "id": "14bbd889-32f4-40dd-92ad-75d35028f874",
      "code": "INV_xxxxxxxxxxxxx",
      "amount": 5000,
      "currency": "NGN",
      "status": "PAYMENT_SUCCEEDED",
      "retryCount": 0
    }
  },
  "meta": {
    "mode": "live",
    "kyshiEventId": "evt_xxxxx",
    "kyshiWebhookQueuedAt": "2026-06-01T00:01:00.000Z"
  }
}

Recommended Handling

When you receive a subscription webhook:

  1. Verify the webhook signature.
  2. Use meta.kyshiEventId for idempotency.
  3. Read event.
  4. Update the subscription record in your system.
  5. Grant or revoke access based on subscription.status and subscription.isActive.

Access Rule

Grant access only when:

  • subscription.status = ACTIVE and subscription.isActive = true

Block Access When

  • subscription.status = PAST_DUE
  • subscription.status = CANCELLED
  • subscription.status = COMPLETED
  • subscription.isActive = false

NON_RENEWING Behavior

  • Allow access until subscription.currentPeriodEnd

Idempotency

Webhook events can be retried. Your webhook handler should be idempotent.

Use:

  • meta.kyshiEventId

to detect duplicate events.


Important Notes

  • invoice.payment_failed means payment failed for a billing cycle.
  • subscription.past_due means the subscription should no longer have access.
  • invoice.payment_succeeded means payment was successful for the invoice.
  • subscription.active means access may be restored.
  • subscription.cancelled means the subscription should no longer renew.
  • subscription.completed means all configured billing cycles are complete.