jackdbd / webhooks

Application that I use to process webhook events fired by several 3rd party services

Home Page:https://webhooks.giacomodebidda.com

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

webhooks 🪝

Application that I use to process webhook events fired by several services: Cloud Monitoring, npm.js, Stripe, etc. All webhooks are hosted as a single application on Cloudflare Pages. Some routes are handled by the Cloudflare Pages Functions routing. Some others are handled by Hono.

⚠️ Warning:

Don't use wrangler 3 until this bug is fixed.

Installation

npm install

Development

When developing handlers for Stripe webhooks, you will need 2 terminals open to develop this application. In all other cases you will need 3 terminals open. I use Tmux for this.

Environment variables & secrets

When developing an app for Cloudflare Workers or Cloudflare Pages with wrangler dev, you can set environment variables and secrets in a .dev.vars file. This file must be kept in the root directory of your project. Given that some secrets might be JSON strings, I like to keep them the secrets directory. Then I generate the .dev.vars file using this script:

node scripts/make-dev-vars.mjs
# in alternative, run this npm script:
npm run make-dev-vars

Stripe webhooks

First of all, create a Stripe webhook endpoint for you Stripe account in test mode, and your Stripe account in live mode. Double check that you have created and enabled such endpoints:

stripe webhook_endpoints list --api-key $STRIPE_API_KEY_TEST
stripe webhook_endpoints list --api-key $STRIPE_API_KEY_LIVE

In the first terminal, run this command, which watches all files using wrangler and forwards all Stripe webhook events to localhost:8788 using the Stripe CLI:

npm run dev

The main web page will be available at: http://localhost:8788/

In the second terminal, trigger some Stripe events:

stripe trigger --api-key $STRIPE_API_KEY_TEST customer.created
stripe trigger --api-key $STRIPE_API_KEY_TEST payment_intent.succeeded
stripe trigger --api-key $STRIPE_API_KEY_TEST price.created
stripe trigger --api-key $STRIPE_API_KEY_TEST product.created

API_KEY=$(cat secrets/stripe-webhook-endpoint-live.json | jq '.api_key') && \
SIGNING_SECRET=$(cat secrets/stripe-webhook-endpoint-live.json | jq '.signing_secret') &&
echo "API key is ${API_KEY} and secret is ${SIGNING_SECRET}"

stripe trigger --api-key $STRIPE_API_KEY_RESTRICTED customer.created

Or make some POST requests manually:

POST to the test endpoint without required header and invalid data:

curl "http://localhost:8788/stripe" \
  -X POST \
  -H "Content-Type: application/json" \
  -d '{"foo": "bar", "baz": 123}' | jq

POST to the test endpoint with the required header but invalid data:

curl "http://localhost:8788/stripe" \
  -X POST \
  -H "Content-Type: application/json" \
  -H "stripe-signature: foobar" \
  -d '{"foo": "bar", "baz": 123}' | jq

POST to the test endpoint with the required header and valid data:

curl "http://localhost:8788/stripe" \
  -X POST \
  -H "Content-Type: application/json" \
  -H "stripe-signature: foobar" \
  -d "@./assets/webhook-events/stripe/customer-created.json" | jq

POST to the live endpoint with invalid data:

STRIPE_WEBHOOKS_ENDPOINT=$(
  cat secrets/stripe-webhook-endpoint-live.json | jq '.url' | tr -d '"'
) && \
curl $STRIPE_WEBHOOKS_ENDPOINT \
  -X POST \
  -H "Content-Type: application/json" \
  -d '{
    "foo": "bar",
    "baz": 123
  }' | jq

Also, send a GET request to see list of all events that Stripe is allowed to send to this endpoint:

curl "http://localhost:8788/stripe" \
  -X GET \
  -H "Content-Type: application/json" | jq

Instructions for all webhooks except the ones from Stripe

In the first terminal, run this command:

npm run dev

The main web page will be available at: http://localhost:8788/

In the second terminal, run this command, which create a HTTPS => HTTP tunnel with ngrok on port 8788:

ngrok http 8788
# in alternative, run this npm script:
npm run tunnel

Now copy the public, Forwarding URL that ngrok gave you, and assign it to the WEBHOOKS_URL environment variable (for example, paste it in your .envrc file and reload it with direnv allow). Be sure not to include any trailing slashes.

Setting up a HTTP tunnel with ngrok

ℹ️ Note:

Now you can also:

In the third terminal, make some POST requests simulating webhook events sent by a third-party service. See a few examples below.

cal.com webhooks

See the documentation on cal.com.

Cal.com webhooks configuration

curl "http://localhost:8788/cal" \
  -X POST \
  -H "Content-Type: application/json" \
  -d '{"foo": 123, "bar": 456}' | jq
curl "http://localhost:8788/cal" \
  -X POST \
  -H "Content-Type: application/json" \
  -H "X-Cal-Signature-256: hex-string-sent-by-cal.com" \
  -d '{"foo": 123, "bar": 456}' | jq
curl "http://localhost:8788/cal" \
  -X POST \
  -H "Content-Type: application/json" \
  -H "X-Cal-Signature-256: hex-string-sent-by-cal.com" \
  -d "@./assets/webhook-events/cal/booking-created.json" | jq

Create a new booking:

curl "$WEBHOOKS_URL/cal" \
  -X POST \
  -H "Content-Type: application/json" \
  -H "X-Cal-Signature-256: hex-string-sent-by-cal.com" \
  -d "@./assets/webhook-events/cal/booking-created.json" | jq

Reschedule a booking:

curl "$WEBHOOKS_URL/cal" \
  -X POST \
  -H "Content-Type: application/json" \
  -H "X-Cal-Signature-256: hex-string-sent-by-cal.com" \
  -d "@./assets/webhook-events/cal/booking-rescheduled.json" | jq

Cancel a booking:

curl "$WEBHOOKS_URL/cal" \
  -X POST \
  -H "Content-Type: application/json" \
  -H "X-Cal-Signature-256: hex-string-sent-by-cal.com" \
  -d "@./assets/webhook-events/cal/booking-cancelled.json" | jq

Event sent by cal.com when a meeting ends:

curl "$WEBHOOKS_URL/cal" \
  -X POST \
  -H "Content-Type: application/json" \
  -H "X-Cal-Signature-256: hex-string-sent-by-cal.com" \
  -d "@./assets/webhook-events/cal/meeting-ended.json" | jq

Cloudinary webhooks

See the documentation on Cloudinary.

Missing headers, invalid data:

curl "http://localhost:8788/cloudinary" \
  -X POST \
  -H "Content-Type: application/json" \
  -d '{"foo": 123, "bar": 456}' | jq

Required headers, invalid data:

curl "http://localhost:8788/cloudinary" \
  -X POST \
  -H "Content-Type: application/json" \
  -H "X-Cld-Signature: signature-sent-by-cloudinary" \
  -H "X-Cld-Timestamp: 1685819601" \
  -d '{"foo": 123, "bar": 456}' | jq

Required headers, valid data:

curl "http://localhost:8788/cloudinary" \
  -X POST \
  -H "Content-Type: application/json" \
  -H "X-Cld-Signature: signature-sent-by-cloudinary" \
  -H "X-Cld-Timestamp: 1685819601" \
  -d "@./assets/webhook-events/cloudinary/image-uploaded.json" | jq
curl "$WEBHOOKS_URL/cloudinary" \
  -X POST \
  -H "Content-Type: application/json" \
  -H "X-Cld-Signature: signature-sent-by-cloudinary" \
  -H "X-Cld-Timestamp: 1685819601" \
  -d "@./assets/webhook-events/cloudinary/image-uploaded.json" | jq

Cloud Monitoring webhooks

See the documentation on Cloud Monitoring.

Missing headers, invalid data:

A Cloud Monitoring webhook notification channel supports basic access authentication.

Cloud Monitoring requires your server to return a 401 response with the proper WWW-Authenticate header. So we use curl --include or curl --verbose to verify that the server returns the WWW-Authenticate response header.

curl "http://localhost:8788/monitoring" \
  -X POST \
  -H "Content-Type: application/json" \
  -d '{"foo": 123, "bar": 456}' --include

Required headers, invalid data:

curl "http://localhost:8788/monitoring" \
  -X POST \
  -H "Content-Type: application/json" \
  -H "Authorization: Basic $BASE64_ENCODED_BASIC_AUTH" \
  -d '{"foo": 123, "bar": 456}' | jq

Required headers, valid data:

curl "http://localhost:8788/monitoring" \
  -X POST \
  -H "Content-Type: application/json" \
  -H "Authorization: Basic $BASE64_ENCODED_BASIC_AUTH" \
  -d "@./assets/webhook-events/cloud-monitoring/incident-created.json" | jq

Required headers, valid data:

curl "$WEBHOOKS_URL/monitoring" \
  -X POST \
  -H "Content-Type: application/json" \
  -H "Authorization: Basic $BASE64_ENCODED_BASIC_AUTH" \
  -d "@./assets/webhook-events/cloud-monitoring/incident-created.json" | jq

npm.js webhooks

See the documentation on npm.js.

curl "$WEBHOOKS_URL/npm" \
  -X POST \
  -H "Content-Type: application/json" \
  -d '{"foo": 123, "bar": 456}' | jq
curl "$WEBHOOKS_URL/npm" \
  -X POST \
  -H "Content-Type: application/json" \
  -H "x-npm-signature: hex-string-sent-by-npm.js" \
  -d "@./assets/webhook-events/npm/package-changed.json" | jq

WebPageTest pingbacks

See the documentation on WebPageTest.

curl "http://localhost:8788/webpagetest?id=some-webpagetest-test-id" \
  -X GET \
  -H "Content-Type: application/json"

Troubleshooting webhooks

Access your Cloudflare Pages Functions logs by using the Cloudflare dashboard or the Wrangler CLI:

npm run logs
# which is equivalent to:
npx wrangler pages deployment tail --project-name webhooks

See the docs for details.

Deploy

I enabled automatic deployments, so the application is automatically deployed to Cloudflare Pages on each git push (main is the production branch, all other branches are preview branches).

You can also deploy manually using this command:

npm run deploy
# which is equivalent to:
wrangler pages publish ./pages

About

Application that I use to process webhook events fired by several 3rd party services

https://webhooks.giacomodebidda.com


Languages

Language:TypeScript 88.2%Language:JavaScript 7.0%Language:Nix 2.9%Language:HTML 1.9%