Skip to content

feat: implement auto webhook creation #13

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 2 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .env.sample
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
NODE_ENV=production
DATABASE_URL=postgres://postgres:postgres@host:5432/postgres?sslmode=disable&search_path=stripe
STRIPE_SECRET_KEY=sk_test_
STRIPE_WEBHOOK_SECRET=whsec_
STRIPE_WEBHOOK_URL=https://
SCHEMA=stripe
3 changes: 2 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -78,12 +78,13 @@ This server synchronizes your Stripe account to a Postgres database. It can be a

## Usage

- Update your Stripe account with all valid webhooks and get the webhook secret
- Update STRIPE_WEBHOOK_URL with publicly available url of your webhook endpoint.
- `mv .env.sample .env` and then rename all the variables
- Make sure the database URL has search_path `stripe`. eg: `DATABASE_URL=postgres://postgres:postgres@hostname:5432/postgres?sslmode=disable&search_path=stripe`
- Run `dbmate up`
- Deploy the [docker image](https://hub.docker.com/r/supabase/stripe-sync-engine) to your favourite hosting service and expose port `8080`
- Point your Stripe webooks to your deployed app.

## Future ideas

- Expose a "sync" endpoint for each table which will manually fetch and sync from Stripe.
Expand Down
2 changes: 1 addition & 1 deletion src/routes/webhooks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ export default async function routes(fastify: FastifyInstance) {

let event
try {
event = stripe.webhooks.constructEvent(body.raw, sig, config.STRIPE_WEBHOOK_SECRET)
event = stripe.webhooks.constructEvent(body.raw, sig, process.env.STRIPE_WEBHOOK_SECRET!)
} catch (err) {
return reply.code(400).send(`Webhook Error: ${err.message}`)
}
Expand Down
24 changes: 20 additions & 4 deletions src/server.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { FastifyInstance } from 'fastify'
import { Server, IncomingMessage, ServerResponse } from 'http'
import build from './app'
import { registerWebhooks } from './utils/registerWebhooks'

const loggerConfig = {
prettyPrint: true,
Expand All @@ -15,10 +16,25 @@ const app: FastifyInstance<Server, IncomingMessage, ServerResponse> = build({
exposeDocs,
})

app.listen(8080, '0.0.0.0', (err, address) => {
if (err) {

async function main() {
try {
await registerWebhooks();
console.log(`Webhook initialization was successful.`);
} catch (error) {
console.error(`Refusing to start due to error on webhook initialization.`);
console.error(error.message);
process.exit(1);
}

try {
const address = await app.listen(8080, '0.0.0.0');
console.log(`Server listening at ${address}`);
} catch (err) {
console.error(err)
process.exit(1)
}
console.log(`Server listening at ${address}`)
})

}

main();
5 changes: 4 additions & 1 deletion src/utils/StripeClientManager.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
import Stripe from 'stripe'
import { getConfig } from './config'

export const stripe = new Stripe(process.env.STRIPE_SECRET_KEY || '', {
const config = getConfig();

export const stripe = new Stripe(config.STRIPE_SECRET_KEY, {
// https://github.com/stripe/stripe-node#configuration
apiVersion: '2020-08-27',
appInfo: {
Expand Down
8 changes: 4 additions & 4 deletions src/utils/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,11 @@ type configType = {
NODE_ENV: string
SCHEMA: string
STRIPE_SECRET_KEY: string
STRIPE_WEBHOOK_SECRET: string
STRIPE_WEBHOOK_URL: string
}

function getConfigFromEnv(key: string): string {
const value = process.env[key]
function getConfigFromEnv(key: string, _default?: string): string {
const value = process.env[key];
if (!value) {
throw new Error(`${key} is undefined`)
}
Expand All @@ -24,6 +24,6 @@ export function getConfig(): configType {
SCHEMA: getConfigFromEnv('SCHEMA'),
NODE_ENV: getConfigFromEnv('NODE_ENV'),
STRIPE_SECRET_KEY: getConfigFromEnv('STRIPE_SECRET_KEY'),
STRIPE_WEBHOOK_SECRET: getConfigFromEnv('STRIPE_WEBHOOK_SECRET'),
STRIPE_WEBHOOK_URL: getConfigFromEnv('STRIPE_WEBHOOK_URL')
}
}
81 changes: 81 additions & 0 deletions src/utils/registerWebhooks.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
import { getConfig } from "./config";
import { stripe } from "./StripeClientManager";
import { Stripe } from "stripe";

const enabledEvents: Stripe.WebhookEndpointCreateParams.EnabledEvent[] = [
"customer.created",
"customer.updated",
"customer.subscription.created",
"customer.subscription.deleted",
"customer.subscription.updated",
"invoice.created",
"invoice.finalized",
"invoice.paid",
"invoice.payment_failed",
"invoice.payment_succeeded",
"invoice.updated",
"product.created",
"product.updated",
"product.deleted",
"price.created",
"price.updated",
"price.deleted",
];

const config = getConfig();

let secret: string | undefined = undefined;

export async function registerWebhooks() {

let webhook: Stripe.WebhookEndpoint | undefined = undefined;

let hooks = await stripe.webhookEndpoints.list();

do {
for (const endpoint of hooks.data) {
if (
!endpoint.metadata ||
!endpoint.metadata.createdByStripePostgresSync ||
endpoint.url != config.STRIPE_WEBHOOK_URL
) {
continue;
}

if (endpoint.enabled_events.join() != enabledEvents.join()) {
console.debug(`Found a webhook that has different enabled events or url`);
console.debug(`Updating the webhook ${endpoint.url}`);
stripe.webhookEndpoints.update(endpoint.id, {
enabled_events: enabledEvents
})
}

webhook = endpoint;
}

if (hooks.has_more) {
hooks = await stripe.webhookEndpoints.list({
starting_after: hooks.data[hooks.data.length - 1].id
});
}
} while (hooks.has_more);

if (webhook == undefined) {
console.log("There was no webhook matching the url and enabled events.");
console.log(`Creating a new webhook with url ${config.STRIPE_WEBHOOK_URL}`);
webhook = await stripe.webhookEndpoints.create({
url: config.STRIPE_WEBHOOK_URL,
enabled_events: enabledEvents,
description: "Created by Stripe Postgres Sync",
metadata: {
createdByStripePostgresSync: "true"
},
});
}

process.env.STRIPE_WEBHOOK_SECRET = webhook.secret;
}

export function getSecret(): string | undefined {
return secret;
}