@intentsolutionsio/supabase-pack 1.0.0 → 1.0.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (133) hide show
  1. package/LICENSE +1 -1
  2. package/README.md +73 -47
  3. package/package.json +4 -4
  4. package/skills/supabase-advanced-troubleshooting/SKILL.md +404 -200
  5. package/skills/supabase-advanced-troubleshooting/references/errors.md +11 -0
  6. package/skills/supabase-advanced-troubleshooting/references/evidence-collection-framework.md +34 -0
  7. package/skills/supabase-advanced-troubleshooting/references/examples.md +11 -0
  8. package/skills/supabase-advanced-troubleshooting/references/rls-edge-functions-realtime.md +363 -0
  9. package/skills/supabase-advanced-troubleshooting/references/systematic-isolation.md +56 -0
  10. package/skills/supabase-advanced-troubleshooting/references/timing-analysis.md +35 -0
  11. package/skills/supabase-architecture-variants/SKILL.md +395 -216
  12. package/skills/supabase-architecture-variants/references/errors.md +11 -0
  13. package/skills/supabase-architecture-variants/references/examples.md +12 -0
  14. package/skills/supabase-architecture-variants/references/serverless-and-multi-tenant.md +251 -0
  15. package/skills/supabase-architecture-variants/references/variant-a-monolith-(simple).md +44 -0
  16. package/skills/supabase-architecture-variants/references/variant-b-service-layer-(moderate).md +72 -0
  17. package/skills/supabase-architecture-variants/references/variant-c-microservice-(complex).md +81 -0
  18. package/skills/supabase-auth-storage-realtime-core/SKILL.md +471 -37
  19. package/skills/supabase-ci-integration/SKILL.md +315 -67
  20. package/skills/supabase-ci-integration/references/errors.md +10 -0
  21. package/skills/supabase-ci-integration/references/examples.md +36 -0
  22. package/skills/supabase-ci-integration/references/implementation.md +54 -0
  23. package/skills/supabase-common-errors/SKILL.md +320 -62
  24. package/skills/supabase-common-errors/references/errors.md +53 -0
  25. package/skills/supabase-common-errors/references/examples.md +23 -0
  26. package/skills/supabase-cost-tuning/SKILL.md +365 -131
  27. package/skills/supabase-cost-tuning/references/cost-estimation.md +34 -0
  28. package/skills/supabase-cost-tuning/references/cost-reduction-strategies.md +40 -0
  29. package/skills/supabase-cost-tuning/references/errors.md +11 -0
  30. package/skills/supabase-cost-tuning/references/examples.md +15 -0
  31. package/skills/supabase-data-handling/SKILL.md +378 -145
  32. package/skills/supabase-data-handling/references/errors.md +11 -0
  33. package/skills/supabase-data-handling/references/examples.md +27 -0
  34. package/skills/supabase-data-handling/references/implementation.md +223 -0
  35. package/skills/supabase-data-handling/references/retention-and-backup.md +221 -0
  36. package/skills/supabase-debug-bundle/SKILL.md +267 -73
  37. package/skills/supabase-debug-bundle/references/errors.md +12 -0
  38. package/skills/supabase-debug-bundle/references/examples.md +24 -0
  39. package/skills/supabase-debug-bundle/references/implementation.md +54 -0
  40. package/skills/supabase-deploy-integration/SKILL.md +258 -147
  41. package/skills/supabase-deploy-integration/references/errors.md +11 -0
  42. package/skills/supabase-deploy-integration/references/examples.md +21 -0
  43. package/skills/supabase-deploy-integration/references/google-cloud-run.md +36 -0
  44. package/skills/supabase-deploy-integration/references/vercel-deployment.md +35 -0
  45. package/skills/supabase-enterprise-rbac/SKILL.md +327 -160
  46. package/skills/supabase-enterprise-rbac/references/api-scoping-and-enforcement.md +255 -0
  47. package/skills/supabase-enterprise-rbac/references/errors.md +11 -0
  48. package/skills/supabase-enterprise-rbac/references/examples.md +12 -0
  49. package/skills/supabase-enterprise-rbac/references/role-implementation.md +33 -0
  50. package/skills/supabase-enterprise-rbac/references/sso-integration.md +35 -0
  51. package/skills/supabase-hello-world/SKILL.md +160 -54
  52. package/skills/supabase-incident-runbook/SKILL.md +453 -131
  53. package/skills/supabase-incident-runbook/references/errors.md +11 -0
  54. package/skills/supabase-incident-runbook/references/examples.md +10 -0
  55. package/skills/supabase-incident-runbook/references/immediate-actions-by-error-type.md +41 -0
  56. package/skills/supabase-install-auth/SKILL.md +186 -50
  57. package/skills/supabase-install-auth/references/examples.md +102 -0
  58. package/skills/supabase-known-pitfalls/SKILL.md +411 -241
  59. package/skills/supabase-known-pitfalls/references/errors.md +11 -0
  60. package/skills/supabase-known-pitfalls/references/examples.md +12 -0
  61. package/skills/supabase-load-scale/SKILL.md +346 -217
  62. package/skills/supabase-load-scale/references/capacity-planning.md +47 -0
  63. package/skills/supabase-load-scale/references/errors.md +11 -0
  64. package/skills/supabase-load-scale/references/examples.md +26 -0
  65. package/skills/supabase-load-scale/references/load-testing-with-k6.md +59 -0
  66. package/skills/supabase-load-scale/references/scaling-patterns.md +65 -0
  67. package/skills/supabase-load-scale/references/table-partitioning.md +263 -0
  68. package/skills/supabase-local-dev-loop/SKILL.md +272 -73
  69. package/skills/supabase-local-dev-loop/references/errors.md +11 -0
  70. package/skills/supabase-local-dev-loop/references/examples.md +21 -0
  71. package/skills/supabase-local-dev-loop/references/implementation.md +60 -0
  72. package/skills/supabase-migration-deep-dive/SKILL.md +338 -177
  73. package/skills/supabase-migration-deep-dive/references/backfill-versioning-rollback.md +258 -0
  74. package/skills/supabase-migration-deep-dive/references/errors.md +11 -0
  75. package/skills/supabase-migration-deep-dive/references/examples.md +12 -0
  76. package/skills/supabase-migration-deep-dive/references/implementation-plan.md +80 -0
  77. package/skills/supabase-migration-deep-dive/references/pre-migration-assessment.md +39 -0
  78. package/skills/supabase-multi-env-setup/SKILL.md +393 -152
  79. package/skills/supabase-multi-env-setup/references/configuration-structure.md +59 -0
  80. package/skills/supabase-multi-env-setup/references/errors.md +11 -0
  81. package/skills/supabase-multi-env-setup/references/examples.md +11 -0
  82. package/skills/supabase-observability/SKILL.md +318 -196
  83. package/skills/supabase-observability/references/alert-configuration.md +40 -0
  84. package/skills/supabase-observability/references/errors.md +11 -0
  85. package/skills/supabase-observability/references/examples.md +13 -0
  86. package/skills/supabase-observability/references/metrics-collection.md +65 -0
  87. package/skills/supabase-performance-tuning/SKILL.md +304 -160
  88. package/skills/supabase-performance-tuning/references/caching-strategy.md +49 -0
  89. package/skills/supabase-performance-tuning/references/errors.md +11 -0
  90. package/skills/supabase-performance-tuning/references/examples.md +13 -0
  91. package/skills/supabase-policy-guardrails/SKILL.md +248 -221
  92. package/skills/supabase-policy-guardrails/references/ci-cost-security.md +484 -0
  93. package/skills/supabase-policy-guardrails/references/errors.md +11 -0
  94. package/skills/supabase-policy-guardrails/references/eslint-rules.md +46 -0
  95. package/skills/supabase-policy-guardrails/references/examples.md +10 -0
  96. package/skills/supabase-prod-checklist/SKILL.md +474 -84
  97. package/skills/supabase-prod-checklist/references/errors.md +63 -0
  98. package/skills/supabase-prod-checklist/references/examples.md +153 -0
  99. package/skills/supabase-prod-checklist/references/implementation.md +113 -0
  100. package/skills/supabase-rate-limits/SKILL.md +311 -98
  101. package/skills/supabase-rate-limits/references/errors.md +11 -0
  102. package/skills/supabase-rate-limits/references/examples.md +46 -0
  103. package/skills/supabase-rate-limits/references/implementation.md +66 -0
  104. package/skills/supabase-reference-architecture/SKILL.md +249 -182
  105. package/skills/supabase-reference-architecture/references/errors.md +29 -0
  106. package/skills/supabase-reference-architecture/references/examples.md +116 -0
  107. package/skills/supabase-reference-architecture/references/key-components.md +244 -0
  108. package/skills/supabase-reference-architecture/references/project-structure.md +109 -0
  109. package/skills/supabase-reliability-patterns/SKILL.md +229 -234
  110. package/skills/supabase-reliability-patterns/references/circuit-breaker.md +36 -0
  111. package/skills/supabase-reliability-patterns/references/dead-letter-queue.md +48 -0
  112. package/skills/supabase-reliability-patterns/references/errors.md +11 -0
  113. package/skills/supabase-reliability-patterns/references/examples.md +11 -0
  114. package/skills/supabase-reliability-patterns/references/idempotency-keys.md +36 -0
  115. package/skills/supabase-reliability-patterns/references/offline-degradation-health-dualwrite.md +489 -0
  116. package/skills/supabase-schema-from-requirements/SKILL.md +373 -34
  117. package/skills/supabase-sdk-patterns/SKILL.md +388 -99
  118. package/skills/supabase-sdk-patterns/references/errors.md +11 -0
  119. package/skills/supabase-sdk-patterns/references/examples.md +45 -0
  120. package/skills/supabase-sdk-patterns/references/implementation.md +67 -0
  121. package/skills/supabase-security-basics/SKILL.md +282 -102
  122. package/skills/supabase-security-basics/references/errors.md +10 -0
  123. package/skills/supabase-security-basics/references/examples.md +70 -0
  124. package/skills/supabase-security-basics/references/implementation.md +39 -0
  125. package/skills/supabase-upgrade-migration/SKILL.md +248 -66
  126. package/skills/supabase-upgrade-migration/references/errors.md +10 -0
  127. package/skills/supabase-upgrade-migration/references/examples.md +51 -0
  128. package/skills/supabase-upgrade-migration/references/implementation.md +29 -0
  129. package/skills/supabase-webhooks-events/SKILL.md +412 -138
  130. package/skills/supabase-webhooks-events/references/errors.md +55 -0
  131. package/skills/supabase-webhooks-events/references/event-handler-pattern.md +106 -0
  132. package/skills/supabase-webhooks-events/references/examples.md +133 -0
  133. package/skills/supabase-webhooks-events/references/signature-verification.md +165 -0
@@ -0,0 +1,106 @@
1
+ # Event Handler Pattern
2
+
3
+ ## Typed Event Dispatcher
4
+
5
+ A registry-based pattern for routing Supabase webhook events to typed handlers. This eliminates switch statements and makes adding new event handlers declarative.
6
+
7
+ ```typescript
8
+ // types.ts
9
+ type EventType = "INSERT" | "UPDATE" | "DELETE";
10
+
11
+ interface WebhookPayload<T = Record<string, unknown>> {
12
+ type: EventType;
13
+ table: string;
14
+ schema: string;
15
+ record: T;
16
+ old_record: T | null;
17
+ }
18
+
19
+ type EventHandler<T = Record<string, unknown>> = (
20
+ payload: WebhookPayload<T>
21
+ ) => Promise<void>;
22
+
23
+ interface HandlerRegistration {
24
+ table: string;
25
+ event: EventType | "*";
26
+ handler: EventHandler;
27
+ }
28
+ ```
29
+
30
+ ## Dispatcher Implementation
31
+
32
+ ```typescript
33
+ // event-dispatcher.ts
34
+ class EventDispatcher {
35
+ private handlers: HandlerRegistration[] = [];
36
+
37
+ on(table: string, event: EventType | "*", handler: EventHandler): void {
38
+ this.handlers.push({ table, event, handler });
39
+ }
40
+
41
+ async dispatch(payload: WebhookPayload): Promise<void> {
42
+ const matched = this.handlers.filter(
43
+ (h) =>
44
+ h.table === payload.table &&
45
+ (h.event === "*" || h.event === payload.type)
46
+ );
47
+
48
+ if (matched.length === 0) {
49
+ console.warn(
50
+ `No handler for ${payload.type} on ${payload.table}`
51
+ );
52
+ return;
53
+ }
54
+
55
+ // Run all matched handlers concurrently
56
+ const results = await Promise.allSettled(
57
+ matched.map((h) => h.handler(payload))
58
+ );
59
+
60
+ for (const result of results) {
61
+ if (result.status === "rejected") {
62
+ console.error("Handler failed:", result.reason);
63
+ }
64
+ }
65
+ }
66
+ }
67
+ ```
68
+
69
+ ## Usage
70
+
71
+ ```typescript
72
+ // Register handlers
73
+ const dispatcher = new EventDispatcher();
74
+
75
+ dispatcher.on("orders", "INSERT", async (payload) => {
76
+ await sendOrderConfirmationEmail(payload.record.email);
77
+ await updateInventoryCount(payload.record.product_id, -1);
78
+ });
79
+
80
+ dispatcher.on("orders", "UPDATE", async (payload) => {
81
+ if (payload.old_record?.status !== payload.record.status) {
82
+ await notifyStatusChange(payload.record);
83
+ }
84
+ });
85
+
86
+ dispatcher.on("profiles", "*", async (payload) => {
87
+ await syncToExternalCRM(payload.record);
88
+ });
89
+
90
+ // In your Edge Function serve() handler:
91
+ serve(async (req) => {
92
+ const payload: WebhookPayload = await req.json();
93
+ await dispatcher.dispatch(payload);
94
+ return new Response(JSON.stringify({ ok: true }));
95
+ });
96
+ ```
97
+
98
+ ## Benefits
99
+
100
+ - **Type safety**: each handler receives typed payloads
101
+ - **Separation of concerns**: one handler per side effect
102
+ - **Testable**: handlers are pure async functions, easy to unit test
103
+ - **Extensible**: add new handlers without modifying dispatch logic
104
+
105
+ ---
106
+ *[Tons of Skills](https://tonsofskills.com) by [Intent Solutions](https://intentsolutions.io) | [jeremylongshore.com](https://jeremylongshore.com)*
@@ -0,0 +1,133 @@
1
+ # Examples
2
+
3
+ ## Testing Webhooks Locally
4
+
5
+ ### 1. Start Supabase Locally
6
+
7
+ ```bash
8
+ supabase start
9
+ # Note the API URL and service_role key from output
10
+ ```
11
+
12
+ ### 2. Expose Local Edge Function with ngrok
13
+
14
+ ```bash
15
+ # Serve the Edge Function locally
16
+ supabase functions serve on-order-created --env-file .env.local
17
+
18
+ # In another terminal, expose it
19
+ ngrok http 54321
20
+ # Copy the HTTPS URL (e.g., https://abc123.ngrok.io)
21
+ ```
22
+
23
+ ### 3. Create the Trigger Pointing to ngrok
24
+
25
+ ```sql
26
+ -- In local psql or Supabase SQL editor
27
+ CREATE OR REPLACE FUNCTION public.test_webhook()
28
+ RETURNS trigger AS $$
29
+ BEGIN
30
+ PERFORM net.http_post(
31
+ url := 'https://abc123.ngrok.io/functions/v1/on-order-created',
32
+ headers := '{"Content-Type": "application/json"}'::jsonb,
33
+ body := jsonb_build_object('type', TG_OP, 'record', row_to_json(NEW)::jsonb)
34
+ );
35
+ RETURN NEW;
36
+ END;
37
+ $$ LANGUAGE plpgsql SECURITY DEFINER;
38
+
39
+ CREATE TRIGGER test_webhook_trigger
40
+ AFTER INSERT ON public.orders
41
+ FOR EACH ROW EXECUTE FUNCTION public.test_webhook();
42
+ ```
43
+
44
+ ### 4. Trigger the Event
45
+
46
+ ```bash
47
+ # Insert a row to fire the webhook
48
+ curl -X POST 'http://localhost:54321/rest/v1/orders' \
49
+ -H "apikey: <anon-key>" \
50
+ -H "Authorization: Bearer <anon-key>" \
51
+ -H "Content-Type: application/json" \
52
+ -d '{"product_id": 1, "quantity": 2, "status": "pending"}'
53
+ ```
54
+
55
+ ### 5. Verify in ngrok Dashboard
56
+
57
+ Open `http://localhost:4040` to see the webhook request in ngrok's inspector.
58
+
59
+ ## Manual Webhook Testing with curl
60
+
61
+ ```bash
62
+ # Simulate an INSERT webhook payload
63
+ curl -X POST http://localhost:54321/functions/v1/on-order-created \
64
+ -H "Content-Type: application/json" \
65
+ -H "Authorization: Bearer <service-role-key>" \
66
+ -d '{
67
+ "type": "INSERT",
68
+ "table": "orders",
69
+ "schema": "public",
70
+ "record": {"id": 42, "product_id": 1, "quantity": 2, "status": "pending"},
71
+ "old_record": null
72
+ }'
73
+
74
+ # Simulate an UPDATE webhook payload
75
+ curl -X POST http://localhost:54321/functions/v1/on-order-created \
76
+ -H "Content-Type: application/json" \
77
+ -H "Authorization: Bearer <service-role-key>" \
78
+ -d '{
79
+ "type": "UPDATE",
80
+ "table": "orders",
81
+ "schema": "public",
82
+ "record": {"id": 42, "product_id": 1, "quantity": 2, "status": "shipped"},
83
+ "old_record": {"id": 42, "product_id": 1, "quantity": 2, "status": "pending"}
84
+ }'
85
+ ```
86
+
87
+ ## Realtime Subscription Test (Browser Console)
88
+
89
+ ```javascript
90
+ // Paste in browser console with Supabase JS loaded
91
+ const { createClient } = supabase;
92
+ const client = createClient(
93
+ "http://localhost:54321",
94
+ "<anon-key>"
95
+ );
96
+
97
+ client
98
+ .channel("test")
99
+ .on("postgres_changes", { event: "*", schema: "public", table: "orders" },
100
+ (payload) => console.log("Realtime event:", payload)
101
+ )
102
+ .subscribe((status) => console.log("Status:", status));
103
+
104
+ // Now insert a row via curl or the dashboard — you should see the event logged
105
+ ```
106
+
107
+ ## Auth Hook Test (Custom JWT Claims)
108
+
109
+ ```sql
110
+ -- Create a user role mapping
111
+ INSERT INTO public.user_roles (user_id, role) VALUES
112
+ ('<user-uuid>', 'admin');
113
+
114
+ -- Create the custom claims hook
115
+ CREATE OR REPLACE FUNCTION public.custom_access_token_hook(event jsonb)
116
+ RETURNS jsonb AS $$
117
+ DECLARE
118
+ user_role text;
119
+ BEGIN
120
+ SELECT role INTO user_role
121
+ FROM public.user_roles
122
+ WHERE user_id = (event->>'user_id')::uuid;
123
+
124
+ event := jsonb_set(event, '{claims,user_role}', to_jsonb(COALESCE(user_role, 'user')));
125
+ RETURN event;
126
+ END;
127
+ $$ LANGUAGE plpgsql STABLE;
128
+
129
+ -- Enable in Dashboard > Auth > Hooks > Custom Access Token
130
+ ```
131
+
132
+ ---
133
+ *[Tons of Skills](https://tonsofskills.com) by [Intent Solutions](https://intentsolutions.io) | [jeremylongshore.com](https://jeremylongshore.com)*
@@ -0,0 +1,165 @@
1
+ # Signature Verification
2
+
3
+ ## Why Verify Webhook Signatures
4
+
5
+ Any public Edge Function URL can receive HTTP requests from anyone. Without signature verification, an attacker could send fake webhook payloads to trigger unintended side effects (creating orders, modifying data, sending emails). Always verify the HMAC signature before processing.
6
+
7
+ ## Deno (Edge Functions)
8
+
9
+ ```typescript
10
+ // Deno-native using Web Crypto API (no npm dependencies)
11
+ async function verifyWebhookSignature(
12
+ rawBody: string,
13
+ signature: string,
14
+ secret: string
15
+ ): Promise<boolean> {
16
+ const encoder = new TextEncoder();
17
+
18
+ // Import the secret as an HMAC key
19
+ const key = await crypto.subtle.importKey(
20
+ "raw",
21
+ encoder.encode(secret),
22
+ { name: "HMAC", hash: "SHA-256" },
23
+ false,
24
+ ["sign"]
25
+ );
26
+
27
+ // Sign the raw body
28
+ const signed = await crypto.subtle.sign(
29
+ "HMAC",
30
+ key,
31
+ encoder.encode(rawBody)
32
+ );
33
+
34
+ // Convert to hex string
35
+ const expected = Array.from(new Uint8Array(signed))
36
+ .map((b) => b.toString(16).padStart(2, "0"))
37
+ .join("");
38
+
39
+ // Constant-time comparison to prevent timing attacks
40
+ if (signature.length !== expected.length) return false;
41
+ let mismatch = 0;
42
+ for (let i = 0; i < signature.length; i++) {
43
+ mismatch |= signature.charCodeAt(i) ^ expected.charCodeAt(i);
44
+ }
45
+ return mismatch === 0;
46
+ }
47
+
48
+ // Usage in Edge Function
49
+ serve(async (req) => {
50
+ const secret = Deno.env.get("WEBHOOK_SECRET")!;
51
+ const body = await req.text();
52
+ const sig = req.headers.get("x-webhook-signature") ?? "";
53
+
54
+ if (!(await verifyWebhookSignature(body, sig, secret))) {
55
+ return new Response("Unauthorized", { status: 401 });
56
+ }
57
+
58
+ const payload = JSON.parse(body);
59
+ // ... process verified payload
60
+ });
61
+ ```
62
+
63
+ ## Node.js (External Webhook Receivers)
64
+
65
+ ```typescript
66
+ import crypto from "node:crypto";
67
+
68
+ function verifySupabaseSignature(
69
+ rawBody: Buffer | string,
70
+ signature: string,
71
+ timestamp: string,
72
+ secret: string
73
+ ): boolean {
74
+ // Reject old timestamps (replay attack protection — 5 min window)
75
+ const age = Date.now() - parseInt(timestamp, 10) * 1000;
76
+ if (age > 300_000) {
77
+ console.error("Webhook timestamp too old:", age, "ms");
78
+ return false;
79
+ }
80
+
81
+ // Compute expected HMAC using timestamp.body format
82
+ const signedPayload = `${timestamp}.${rawBody.toString()}`;
83
+ const expected = crypto
84
+ .createHmac("sha256", secret)
85
+ .update(signedPayload)
86
+ .digest("hex");
87
+
88
+ // Timing-safe comparison
89
+ return crypto.timingSafeEqual(
90
+ Buffer.from(signature),
91
+ Buffer.from(expected)
92
+ );
93
+ }
94
+
95
+ // Usage in Express
96
+ app.post("/webhook/supabase", (req, res) => {
97
+ const signature = req.headers["x-webhook-signature"] as string;
98
+ const timestamp = req.headers["x-webhook-timestamp"] as string;
99
+
100
+ if (!verifySupabaseSignature(req.body, signature, timestamp, WEBHOOK_SECRET)) {
101
+ return res.status(401).json({ error: "Invalid signature" });
102
+ }
103
+
104
+ const payload = JSON.parse(req.body.toString());
105
+ // ... process verified payload
106
+ res.json({ ok: true });
107
+ });
108
+ ```
109
+
110
+ ## Generating the Webhook Secret
111
+
112
+ ```bash
113
+ # Generate a secure random secret
114
+ openssl rand -hex 32
115
+ # Example: a1b2c3d4e5f6...
116
+
117
+ # Set as Edge Function secret
118
+ supabase secrets set WEBHOOK_SECRET=a1b2c3d4e5f6...
119
+
120
+ # Set in the trigger function (use Supabase Vault for production)
121
+ -- Dashboard > SQL Editor
122
+ SELECT vault.create_secret('a1b2c3d4e5f6...', 'webhook_secret');
123
+ ```
124
+
125
+ ## Signing Outgoing Webhooks from Triggers
126
+
127
+ If you need the trigger to sign the payload so the receiver can verify:
128
+
129
+ ```sql
130
+ CREATE OR REPLACE FUNCTION public.signed_webhook()
131
+ RETURNS trigger AS $$
132
+ DECLARE
133
+ payload jsonb;
134
+ secret text;
135
+ signature text;
136
+ BEGIN
137
+ payload := jsonb_build_object('type', TG_OP, 'record', row_to_json(NEW)::jsonb);
138
+
139
+ -- Retrieve secret from Vault
140
+ SELECT decrypted_secret INTO secret
141
+ FROM vault.decrypted_secrets
142
+ WHERE name = 'webhook_secret';
143
+
144
+ -- Compute HMAC-SHA256 signature
145
+ signature := encode(
146
+ hmac(payload::text, secret, 'sha256'),
147
+ 'hex'
148
+ );
149
+
150
+ PERFORM net.http_post(
151
+ url := 'https://your-receiver.com/webhook',
152
+ headers := jsonb_build_object(
153
+ 'Content-Type', 'application/json',
154
+ 'x-webhook-signature', signature
155
+ ),
156
+ body := payload
157
+ );
158
+
159
+ RETURN NEW;
160
+ END;
161
+ $$ LANGUAGE plpgsql SECURITY DEFINER;
162
+ ```
163
+
164
+ ---
165
+ *[Tons of Skills](https://tonsofskills.com) by [Intent Solutions](https://intentsolutions.io) | [jeremylongshore.com](https://jeremylongshore.com)*