RevKeen Docs
Developers

Webhooks

Receive real-time event notifications via HTTP callbacks

Webhooks enable your application to receive real-time HTTP notifications when events occur in your RevKeen account. Instead of polling the API, webhooks push data to you.

How Webhooks Work

  1. Configure an endpoint -- Register a URL in your dashboard
  2. Event occurs -- Something happens (payment, subscription change, etc.)
  3. RevKeen sends POST -- We send a JSON payload to your URL
  4. You respond -- Return 200 OK to acknowledge receipt
  5. We retry if needed -- Failed deliveries are retried automatically

Webhook Payload

All webhooks have a consistent structure:

{
  "id": "evt_xxxxxxxx",
  "type": "invoice.paid",
  "created": "2026-01-15T10:30:00Z",
  "data": {
    "id": "inv_xxxxxxxx",
    "customerId": "cus_xxxxxxxx",
    "status": "paid",
    "totalMinor": 9900,
    "currency": "USD"
  }
}
FieldTypeDescription
idstringUnique event identifier
typestringEvent type (e.g., invoice.paid)
createdstringISO 8601 timestamp
dataobjectEvent payload (varies by type)

Setting Up Webhooks

Via Dashboard

  1. Go to Settings > Webhooks in your dashboard
  2. Click Add Endpoint
  3. Enter your endpoint URL
  4. Select events to subscribe to
  5. Copy the signing secret for verification

Via API

cURL
curl -X POST "https://api.revkeen.com/v1/webhooks" \
  -H "Authorization: Bearer rk_live_your_api_key" \
  -H "Content-Type: application/json" \
  -d '{
    "url": "https://yourapp.com/webhooks/revkeen",
    "events": ["invoice.paid", "subscription.created", "subscription.cancelled"]
  }'
TypeScript
const webhook = await client.webhooks.create({
  url: 'https://yourapp.com/webhooks/revkeen',
  events: [
    'invoice.paid',
    'subscription.created',
    'subscription.cancelled',
  ],
});

// Store the signing secret securely
console.log(webhook.data.secret);
Python
webhook = client.webhooks.create(
    url="https://yourapp.com/webhooks/revkeen",
    events=[
        "invoice.paid",
        "subscription.created",
        "subscription.cancelled"
    ]
)

# Store the signing secret securely
print(webhook.data.secret)

Verifying Signatures

Always verify webhook signatures to ensure requests are from RevKeen.

Signature Header

Every webhook includes a X-RevKeen-Signature header:

X-RevKeen-Signature: t=1673856000,v1=5d2c67...abc123

TypeScript Verification

import crypto from 'crypto';

function verifyWebhook(
  payload: string,
  signature: string,
  secret: string
): boolean {
  const [timestamp, sig] = signature.split(',').map(s => s.split('=')[1]);

  // Reject old timestamps (>5 minutes)
  const age = Date.now() - parseInt(timestamp) * 1000;
  if (age > 300000) {
    throw new Error('Webhook timestamp too old');
  }

  // Compute expected signature
  const expected = crypto
    .createHmac('sha256', secret)
    .update(`${timestamp}.${payload}`)
    .digest('hex');

  // Constant-time comparison
  return crypto.timingSafeEqual(
    Buffer.from(sig),
    Buffer.from(expected)
  );
}

// Express.js example
app.post('/webhooks/revkeen', express.raw({ type: 'application/json' }), (req, res) => {
  const signature = req.headers['x-revkeen-signature'] as string;
  const payload = req.body.toString();

  try {
    if (!verifyWebhook(payload, signature, process.env.WEBHOOK_SECRET!)) {
      return res.status(401).send('Invalid signature');
    }

    const event = JSON.parse(payload);

    switch (event.type) {
      case 'invoice.paid':
        handleInvoicePaid(event.data);
        break;
      case 'subscription.cancelled':
        handleSubscriptionCancelled(event.data);
        break;
    }

    res.json({ received: true });
  } catch (err) {
    res.status(400).send(`Webhook Error: ${err.message}`);
  }
});

Python Verification

import hmac
import hashlib
import time
import json
from fastapi import FastAPI, Request, HTTPException

def verify_webhook(payload: str, signature: str, secret: str) -> bool:
    parts = dict(p.split('=') for p in signature.split(','))
    timestamp = parts['t']
    sig = parts['v1']

    # Reject old timestamps (>5 minutes)
    age = time.time() - int(timestamp)
    if age > 300:
        raise ValueError('Webhook timestamp too old')

    # Compute expected signature
    expected = hmac.new(
        secret.encode(),
        f'{timestamp}.{payload}'.encode(),
        hashlib.sha256
    ).hexdigest()

    # Constant-time comparison
    return hmac.compare_digest(sig, expected)

@app.post('/webhooks/revkeen')
async def webhook_handler(request: Request):
    signature = request.headers.get('x-revkeen-signature')
    payload = await request.body()

    if not verify_webhook(payload.decode(), signature, WEBHOOK_SECRET):
        raise HTTPException(status_code=401, detail='Invalid signature')

    event = json.loads(payload)

    if event['type'] == 'invoice.paid':
        await handle_invoice_paid(event['data'])
    elif event['type'] == 'subscription.cancelled':
        await handle_subscription_cancelled(event['data'])

    return {'received': True}

Best Practices

  • Respond quickly -- Return 200 OK as soon as possible. Process events asynchronously using a queue.
  • Handle duplicates -- Events may be delivered multiple times. Use the event id for idempotency.
  • Verify signatures -- Always verify X-RevKeen-Signature to ensure webhooks are authentic.
  • Use HTTPS -- Only register HTTPS endpoints. HTTP endpoints will be rejected.
  • Log everything -- Log incoming webhooks for debugging and audit purposes.
  • Test with CLI -- Use revkeen webhook test to send test events to your local endpoint.

Retry Policy

If your endpoint does not return 2xx:

AttemptDelay
1Immediate
25 minutes
330 minutes
42 hours
58 hours
624 hours

After 6 failed attempts, the event is marked as failed. View failed events in your dashboard.

Event Naming Convention

Events follow the pattern: resource.action

  • customer.created -- A customer was created
  • invoice.paid -- An invoice was paid
  • subscription.canceled -- A subscription was canceled

You can subscribe to event patterns like invoice.* to receive all invoice events, or * to receive all events.

Event Catalog

Customer Events

EventDescriptionAction Required
customer.createdCustomer was createdSync to CRM
customer.updatedCustomer details changedSync changes
customer.deletedCustomer was deletedClean up data

Invoice Events

EventDescriptionAction Required
invoice.createdInvoice created-
invoice.updatedInvoice modified-
invoice.finalizedInvoice locked for payment-
invoice.sentInvoice emailed-
invoice.viewedCustomer viewed invoice-
invoice.paidInvoice fully paidProvision service
invoice.payment_failedPayment attempt failedNotify customer
invoice.past_dueInvoice past due dateSend reminders
invoice.voidedInvoice voidedCancel fulfillment
invoice.approvedInvoice approvedSend to customer
invoice.partially_paidPartial payment received-
invoice.due_soonDue date approachingSend reminder
invoice.uncollectibleMarked as bad debtWrite off

Subscription Events

EventDescriptionAction Required
subscription.createdSubscription createdWait for activation
subscription.activatedSubscription activeGrant access
subscription.updatedSubscription changedUpdate access level
subscription.renewedSubscription renewedExtend access
subscription.canceledSubscription canceledAccess until period end
subscription.pausedSubscription pausedSuspend access
subscription.resumedSubscription resumedRestore access
subscription.trial_will_endTrial ends in 3 daysNotify customer
subscription.past_duePayment overdueStart dunning
subscription.unpaidAll retries failedSuspend access
subscription.expiredTerm ended without renewalRevoke access

Payment Events

EventDescriptionAction Required
payment.succeededPayment capturedProvision service
payment.failedPayment failedRetry or notify
payment.refundedPayment refundedUpdate fulfillment
payment.capturedAuthorization captured-
payment.disputedChargeback receivedSubmit evidence
payment.refund_failedRefund failedManual intervention

Payment Method Events

EventDescriptionAction Required
payment_method.createdCard saved-
payment_method.updatedCard updated (ACU)-
payment_method.deletedCard removed-
payment_method.expiringCard expiring soonRequest new card
payment_method.expiredCard expiredNotify customer
payment_method.failedCard validation failedRequest new card
EventDescriptionAction Required
payment_link.createdPayment link created-
payment_link.viewedLink viewed-
payment_link.completedLink completedFulfill order
payment_link.expiredLink expired-

Checkout Session Events

EventDescriptionAction Required
checkout.session.createdSession initiated-
checkout.session.completedCheckout completedFulfill order
checkout.session.expiredSession expiredCart recovery

Void Events

EventDescriptionAction Required
void.createdVoid initiated-
void.succeededVoid completedUpdate records
void.failedVoid failedTry refund instead

Order Events

EventDescriptionAction Required
order.createdOrder created-
order.paidOrder paidBegin fulfillment
order.fulfilledAll items delivered-
order.partially_fulfilledSome items delivered-
order.canceledOrder canceledStop fulfillment
order.updatedOrder modified-

Product Events

EventDescriptionAction Required
product.createdProduct createdSync catalog
product.updatedProduct updatedSync changes
product.deletedProduct deletedRemove from catalog

Price Events

EventDescriptionAction Required
price.createdPrice createdUpdate pricing
price.updatedPrice updatedUpdate pricing
price.deletedPrice deletedRemove pricing

Discount Events

EventDescriptionAction Required
discount.createdDiscount created-
discount.updatedDiscount updated-
discount.deletedDiscount deleted-
discount.redeemedDiscount usedTrack usage

Event Count by Category

CategoryCount
Customer3
Invoice13
Subscription11
Payment6
Payment Method6
Payment Link4
Checkout Session3
Void3
Order6
Product3
Price3
Discount4
Total65

On this page