Skip to content
Open
31 changes: 31 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
name: CI Tests

on:
push:
branches: [ master ]
pull_request:
branches: [ master ]

jobs:
test:
runs-on: ubuntu-latest

strategy:
matrix:
node-version: [18.x, 20.x, 22.x]

steps:
- name: Checkout repo
uses: actions/checkout@v4

- name: Setup Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v4
with:
node-version: ${{ matrix.node-version }}

- name: Install dependencies
run: npm ci

- name: Run tests
run: npm test

2 changes: 1 addition & 1 deletion .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,6 @@ jobs:
*) echo "ERROR: package.json version must be 3.x.x for v3 tags" && exit 1 ;;
esac
- name: Publish to NPM with dist-tag "latest"
run: npm run prepack && npm publish --tag latest --//registry.npmjs.org/:_authToken="$NPM_TOKEN"
run: npm run prepack && npm publish --tag beta --//registry.npmjs.org/:_authToken="$NPM_TOKEN"
env:
NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
1 change: 0 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
node_modules
test*
*js
cjs
esm
Expand Down
12 changes: 12 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,15 @@
### v3.17.0-beta.2 (2025-12-16)

### Enhancements
* `WebhookEventType` is now a runtime enum that can be used for event type comparisons at runtime.
* `WebhookEventType` and `WebhookContentType` are now exported from the main entry points.

### v3.17.0-beta.1 (2025-12-10)

### Enhancements
* Add webhook event handler to process chargebee-events.
* Deprecated `WebhookContentType` class, added `WebhookEventType` class.

### v3.16.0 (2025-12-01)
* * *

Expand Down
168 changes: 168 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -148,6 +148,174 @@ const chargebeeSiteEU = new Chargebee({
});
```

### Handle webhooks

Use the webhook handlers to parse and route webhook payloads from Chargebee with full TypeScript support.

#### Quick Start: Using the default `webhook` instance

The simplest way to handle webhooks is using the pre-configured `webhook` instance:

```typescript
import express from 'express';
import { webhook, type WebhookEvent } from 'chargebee';

const app = express();
app.use(express.json());

webhook.on('subscription_created', async (event: WebhookEvent) => {
console.log(`Subscription created: ${event.id}`);
const subscription = event.content.subscription;
console.log(`Customer: ${subscription.customer_id}`);
});

webhook.on('error', (err: Error) => {
console.error('Webhook error:', err.message);
});

app.post('/chargebee/webhooks', (req, res) => {
webhook.handle(req.body, req.headers);
res.status(200).send('OK');
});

app.listen(8080);
```

**Auto-configured Basic Auth:** The default `webhook` instance automatically configures Basic Auth validation if the following environment variables are set:

- `CHARGEBEE_WEBHOOK_USERNAME` - The expected username
- `CHARGEBEE_WEBHOOK_PASSWORD` - The expected password

When both are present, incoming webhook requests will be validated against these credentials. If not set, no authentication is applied.

#### Creating custom `WebhookHandler` instances

For more control or multiple webhook endpoints, create your own instances:

```typescript
import express from 'express';
import { WebhookHandler, basicAuthValidator } from 'chargebee';

const app = express();
app.use(express.json());

const handler = new WebhookHandler();

// Register event listeners using .on() - events are fully typed
handler.on('subscription_created', async (event) => {
console.log(`Subscription created: ${event.id}`);
const subscription = event.content.subscription;
console.log(`Customer: ${subscription.customer_id}`);
console.log(`Plan: ${subscription.plan_id}`);
});

handler.on('payment_succeeded', async (event) => {
console.log(`Payment succeeded: ${event.id}`);
const transaction = event.content.transaction;
const customer = event.content.customer;
console.log(`Amount: ${transaction.amount}, Customer: ${customer.email}`);
});

// Optional: Add request validator (e.g., Basic Auth)
handler.requestValidator = basicAuthValidator((username, password) => {
return username === 'admin' && password === 'secret';
});

app.post('/chargebee/webhooks', (req, res) => {
handler.handle(req.body, req.headers);
res.status(200).send('OK');
});

app.listen(8080);
```

#### Low-level: Parse and handle events manually

For more control, you can parse webhook events manually:

```typescript
import express from 'express';
import Chargebee, { type WebhookEvent } from 'chargebee';

const app = express();
app.use(express.json());

app.post('/chargebee/webhooks', async (req, res) => {
try {
const event = req.body as WebhookEvent;

switch (event.event_type) {
case 'subscription_created':
// Access event content with proper typing
const subscription = event.content.subscription;
console.log('Subscription created:', subscription.id);
break;

case 'payment_succeeded':
const transaction = event.content.transaction;
console.log('Payment succeeded:', transaction.amount);
break;

default:
console.log('Unhandled event type:', event.event_type);
}

res.status(200).send('OK');
} catch (err) {
console.error('Error processing webhook:', err);
res.status(500).send('Error processing webhook');
}
});

app.listen(8080);
```

#### Handling Unhandled Events

By default, if an incoming webhook event type is not recognized or you haven't registered a corresponding callback handler, the SDK provides flexible options to handle these scenarios:

**Using the `unhandled_event` listener:**

```typescript
import { WebhookHandler } from 'chargebee';

const handler = new WebhookHandler();

handler.on('subscription_created', async (event) => {
// Handle subscription created
});

// Gracefully handle events without registered listeners
handler.on('unhandled_event', async (event) => {
console.log(`Received unhandled event: ${event.event_type}`);
// Log for monitoring or store for later processing
});
```

**Using the `error` listener for error handling:**

If an error occurs during webhook processing (e.g., invalid JSON, validator failure), the SDK will emit an `error` event:

```typescript
const handler = new WebhookHandler();

handler.on('subscription_created', async (event) => {
// Handle subscription created
});

// Catch any errors during webhook processing
handler.on('error', (err) => {
console.error('Webhook processing error:', err);
// Log to monitoring service, alert team, etc.
});
```

**Best Practices:**

- Use `unhandled_event` listener to acknowledge unknown events (return 200 OK) and log them
- Use `error` listener to catch and handle exceptions thrown during event processing
- Both listeners help ensure your webhook endpoint remains stable even when new event types are introduced by Chargebee

### Processing Webhooks - API Version Check

An attribute `api_version` is added to the [Event](https://apidocs.chargebee.com/docs/api/events) resource, which indicates the API version based on which the event content is structured. In your webhook servers, ensure this `api_version` is the same as the [API version](https://apidocs.chargebee.com/docs/api#versions) used by your webhook server's client library.
Expand Down
Loading