@better-webhook/gcp-functions
v0.2.0
Published
GCP Cloud Functions integration for better-webhook
Maintainers
Readme
@better-webhook/gcp-functions
GCP Cloud Functions webhooks in one line.
Turn any better-webhook handler into a GCP Cloud Functions HTTP handler. Zero configuration required.
// index.ts
import { http } from "@google-cloud/functions-framework";
import { ragie } from "@better-webhook/ragie";
import { toGCPFunction } from "@better-webhook/gcp-functions";
const webhook = ragie().event("document_status_updated", async (payload) => {
console.log(`Document ${payload.document_id} is now ${payload.status}`);
});
http("webhookHandler", toGCPFunction(webhook));That's it. Your webhook endpoint is ready.
Features
- ⚡ Zero config — Works out of the box with Cloud Functions
- 🔒 Automatic verification — Signatures verified before your handler runs
- 📝 Type safe — Full TypeScript support
- 🎯 Clean API — One function, one line
- ☁️ Gen 1 & Gen 2 — Supports both Cloud Functions generations
- 📦 Functions Framework v3 & v4 — Compatible with latest
@google-cloud/functions-framework
Installation
npm install @better-webhook/gcp-functions @better-webhook/core
# or
pnpm add @better-webhook/gcp-functions @better-webhook/core
# or
yarn add @better-webhook/gcp-functions @better-webhook/coreQuick Start
1. Install a provider
npm install @better-webhook/ragie2. Create your Cloud Function
2nd Generation (recommended):
// index.ts
import { http } from "@google-cloud/functions-framework";
import { ragie } from "@better-webhook/ragie";
import { toGCPFunction } from "@better-webhook/gcp-functions";
const webhook = ragie({ secret: process.env.RAGIE_WEBHOOK_SECRET })
.event("document_status_updated", async (payload) => {
if (payload.status === "ready") {
await notifyDocumentReady(payload.document_id);
}
})
.event("connection_sync_finished", async (payload) => {
console.log(`Sync ${payload.sync_id} completed`);
});
http("webhookHandler", toGCPFunction(webhook));1st Generation (exports style):
// index.ts
import { ragie } from "@better-webhook/ragie";
import { toGCPFunction } from "@better-webhook/gcp-functions";
const webhook = ragie({ secret: process.env.RAGIE_WEBHOOK_SECRET }).event(
"document_status_updated",
async (payload) => {
console.log(`Document ${payload.document_id} status: ${payload.status}`);
}
);
export const webhookHandler = toGCPFunction(webhook);3. Set your secret
Add the secret to your Cloud Function environment variables:
gcloud functions deploy webhookHandler \
--runtime nodejs20 \
--trigger-http \
--set-env-vars RAGIE_WEBHOOK_SECRET=your-secret-hereDone! Point your webhook provider to your Cloud Function URL.
Handler Context
Every handler receives a second parameter with metadata about the webhook request:
const webhook = ragie().event(
"document_status_updated",
async (payload, context) => {
// Access provider info
console.log(`Provider: ${context.provider}`); // "ragie"
console.log(`Event: ${context.eventType}`); // "document_status_updated"
// Access headers
console.log(`Content-Type: ${context.headers["content-type"]}`);
// Timestamp when webhook was received
console.log(`Received at: ${context.receivedAt.toISOString()}`);
await processDocument(payload);
}
);
http("webhookHandler", toGCPFunction(webhook));Context Properties
| Property | Type | Description |
| ------------ | --------- | ---------------------------------------------------- |
| eventType | string | Event type (e.g., "document_status_updated") |
| provider | string | Provider name (e.g., "ragie") |
| headers | Headers | Request headers (lowercase keys) |
| rawBody | string | Raw request body |
| receivedAt | Date | Timestamp when webhook was received |
Error Handling
Handle errors gracefully:
const webhook = ragie()
.event("document_status_updated", async (payload, context) => {
console.log(`[${context.eventType}] Processing document...`);
await processDocument(payload);
})
.onError((error, context) => {
// Log to your error tracking service
console.error(`Webhook failed: ${context.eventType}`, error);
})
.onVerificationFailed((reason, headers) => {
// Signature verification failed
console.warn("Verification failed:", reason);
});
http("webhookHandler", toGCPFunction(webhook));Configuration Options
Custom Secret
Override the environment variable:
http(
"webhookHandler",
toGCPFunction(webhook, {
secret: process.env.MY_CUSTOM_SECRET,
})
);Success Callback
Track successful webhook processing:
http(
"webhookHandler",
toGCPFunction(webhook, {
onSuccess: async (eventType) => {
// Log to analytics
await analytics.track("webhook_processed", {
provider: "ragie",
event: eventType,
});
},
})
);Raw Body for Signature Verification
For signature verification to work correctly, the raw request body must be available. GCP Cloud Functions with the Functions Framework provide req.rawBody automatically.
If you're using a custom setup, ensure raw body is preserved:
// The adapter checks for raw body in this order:
// 1. req.rawBody (Functions Framework default)
// 2. Buffer body
// 3. String body
// 4. JSON.stringify(req.body) as fallback (may not match original for signature verification)Response Status Codes
The adapter returns appropriate HTTP status codes:
| Code | Meaning |
| ----- | --------------------------------------------- |
| 200 | Webhook processed successfully |
| 204 | No handler registered for this event type |
| 400 | Invalid JSON body or schema validation failed |
| 401 | Signature verification failed |
| 405 | Method not allowed (non-POST request) |
| 500 | Handler threw an error |
Custom Providers
Works with any better-webhook provider:
import { customWebhook, z } from "@better-webhook/core";
import { toGCPFunction } from "@better-webhook/gcp-functions";
import { http } from "@google-cloud/functions-framework";
const webhook = customWebhook({
name: "my-service",
schemas: {
"user.created": z.object({
userId: z.string(),
email: z.string().email(),
}),
},
getEventType: (headers) => headers["x-event-type"],
}).event("user.created", async (payload, context) => {
console.log(`[${context.eventType}] New user: ${payload.userId}`);
await sendWelcomeEmail(payload.email);
});
http("webhookHandler", toGCPFunction(webhook));Deployment
Using gcloud CLI
gcloud functions deploy webhookHandler \
--gen2 \
--runtime nodejs20 \
--trigger-http \
--allow-unauthenticated \
--entry-point webhookHandler \
--set-env-vars RAGIE_WEBHOOK_SECRET=your-secretUsing Terraform
resource "google_cloudfunctions2_function" "webhook" {
name = "webhook-handler"
location = "us-central1"
build_config {
runtime = "nodejs20"
entry_point = "webhookHandler"
source {
storage_source {
bucket = google_storage_bucket.source.name
object = google_storage_bucket_object.source.name
}
}
}
service_config {
max_instance_count = 10
available_memory = "256M"
timeout_seconds = 60
environment_variables = {
RAGIE_WEBHOOK_SECRET = var.ragie_webhook_secret
}
}
}License
MIT
