How It Works
When an event occurs, Kelviq creates aWebhookEvent and fans it out to all enabled endpoints you have registered for that event type. Each delivery is a single POST request with a JSON body and signed headers.
Kelviq retries failed deliveries up to 3 times with a 60-second delay between attempts. A delivery is considered successful when your endpoint returns a 2xx status code.
Registering an Endpoint
Go to Settings → Webhooks in the Kelviq dashboard to create a webhook endpoint. You will specify:- URL — The public HTTPS URL Kelviq will POST events to.
- Events — The subset of event types you want to subscribe to.
kq_whsec_<random>. Store this secret securely — you will need it to verify signatures.
Inspect and resend deliveries
Open Settings → Webhooks and select an endpoint to view its delivery logs. Each delivery shows the event type, status, response code, and response body. To retry a delivery:- Fix the endpoint or deployment issue that caused the delivery to fail.
- Open the failed delivery in the webhook logs.
- Click Resend.
- Review the new delivery attempt and its response.
Event Types
Checkout, subscription, and payment lifecycle
Checkout and subscription creation
checkout.completed confirms that checkout finished successfully, but it does not include subscription_id. Kelviq creates the subscription after checkout and sends its ID in subscription.created.
Use your customer identifier or checkout metadata to correlate the two events. See Add metadata to checkout for request and webhook examples. Do not treat the checkout redirect as proof that a subscription has been created.
Successful invoice payments
Kelviq sendsinvoice.paid whenever an invoice is paid, including invoices that are created directly with a PAID status. Use this event for payment-driven work such as recording revenue, starting an asynchronous provisioning job, or notifying an internal system.
For request-time access checks, query the customer’s current entitlements instead of relying on a local webhook-derived subscription state.
Failed invoice payments
Kelviq sendsinvoice.payment_failed when a subscription invoice payment fails or requires customer action. Use data.object.subscription_id and the embedded customer to identify the affected account. The invoice status is PAYMENT_FAILED for a failed attempt and can remain OPEN when the payment requires customer action.
This event can be followed by invoice.paid if the customer completes authentication or a later retry succeeds. Handle both events idempotently and treat the latest invoice and subscription state as authoritative.
Subscription end dates
Subscription webhook objects includeend_date inside data.object. Kelviq sets the field when a subscription has been cancelled or is scheduled to cancel on a future date. Otherwise, the value is null.
Use end_date for the subscription’s scheduled or final end. billing_period_end_time describes the current billing period instead and should not be treated as the subscription’s cancellation date.
Failed renewal payments
Kelviq does not currently send a separatepayment.failed event. For a failed card renewal:
- The subscription moves to
past_due. - Kelviq emails the customer a payment link.
- Kelviq sends
subscription.updatedwith the new subscription state. - Kelviq retries collection up to eight times over 14 days.
- If all recovery attempts fail, Kelviq cancels the subscription and sends
subscription.cancelled.
subscription.updated with past_due unless that is your own policy. Use the final subscription status or a live entitlement check to decide when access should end.
Billing period field names
Subscription payloads usebilling_period_start_time and billing_period_end_time. Match these names exactly when parsing dates. They describe the current billing period; use end_date for the subscription’s scheduled or final end.
Plan changes
By default, Kelviq applies a plan change immediately, charges any prorated difference, and sendssubscription.plan_changed. After the generated invoice is paid, Kelviq sends the related order and invoice events.
If you update a subscription with paymentBehavior: "activate_on_payment", the existing subscription remains active while payment is pending. A payment method that requires customer action leaves the updated subscription in incomplete. After payment succeeds, the updated subscription becomes active and the previous subscription becomes superseded. If an immediate charge fails, or the customer does not complete payment before expiry, the updated subscription becomes incomplete_expired and the existing subscription remains active.
Do not provision the new plan from the update API response alone. Read the latest subscription status or query the customer’s current entitlements before changing access.
Partial and full refunds
order.refunded identifies whether the order is partially or fully refunded through its status:
PARTIAL_REFUNDfor a partial refundREFUNDEDfor a full refund
order.refunded payload does not include the refunded amount. Read amount or amount_units from refund.created and refund.updated when you need the exact value.
Request Headers
Every webhook request from Kelviq includes the following headers:Payload Structure
The request body is a JSON object with the following top-level fields:Example: checkout.completed payload
Example: checkout.completed payload
Example: customer.created payload
Example: customer.created payload
Example: customer.updated payload
Example: customer.updated payload
Example: order.created payload
Example: order.created payload
Example: order.updated payload
Example: order.updated payload
Example: order.refunded payload
Example: order.refunded payload
Example: subscription.created payload
Example: subscription.created payload
Example: subscription.updated payload
Example: subscription.updated payload
Example: subscription.cancelled payload
Example: subscription.cancelled payload
Example: subscription.plan_changed payload
Example: subscription.plan_changed payload
Example: feature.usage_alert payload
Example: feature.usage_alert payload
Example: invoice.created payload
Example: invoice.created payload
Example: invoice.paid payload
Example: invoice.paid payload
Example: invoice.payment_failed payload
Example: invoice.payment_failed payload
Example: refund.created payload
Example: refund.created payload
Example: refund.updated payload
Example: refund.updated payload
Example: payment_method.created payload
Example: payment_method.created payload
Example: payment_method.updated payload
Example: payment_method.updated payload
Example: payment_method.deleted payload
Example: payment_method.deleted payload
Verifying Signatures
Kelviq signs every request using HMAC-SHA256. To verify a request:- Read the
webhook-idandwebhook-timestampheaders. - Construct the signed string by concatenating:
{webhook-id}.{webhook-timestamp}.{raw-request-body}(joined with.) - Compute HMAC-SHA256 over the signed string using your endpoint’s signing secret as the key.
- Compare the hex digest to the signature in the
webhook-signatureheader (strip thev1,prefix before comparing). - Reject the request if the signatures do not match.
webhook-timestamp is within a few minutes of your server’s current time to defend against replay attacks.
Code Examples
express.raw() (not express.json()) in Node.js so that req.body contains the unmodified request bytes. Parsing the body before verification will break the signature check.Best Practices
- Return
2xxfast. Acknowledge the webhook immediately and process it asynchronously. Long-running handlers increase the risk of timeouts and duplicate retries. - Make handlers idempotent. The same event may be delivered more than once. Use the top-level event
idor thewebhook-idheader as the deduplication key. - Validate the timestamp. Reject requests where
webhook-timestampis more than 5 minutes from your server’s clock to prevent replay attacks. - Use
hmac.compare_digest/hmac.Equal/crypto.timingSafeEqual. Constant-time comparison prevents timing side-channel attacks. - Store the raw body before parsing. Signature verification operates on the raw bytes, not the deserialized object.
Need Help?
- Email us at hi@kelviq.com
- Book a demo