### Meta Webhook Subscription Handshake Source: https://webhooker.eu/docs/meta Meta sends a GET request to verify the callback URL. Webhooker automatically responds with the hub.challenge value to complete the handshake. ```http # Meta sends this when you click "Verify and save" GET https://app.webhooker.eu/in/YOUR_SOURCE_TOKEN?hub.mode=subscribe&hub.verify_token=any-value&hub.challenge=1158201444 # Webhooker answers with the challenge, byte for byte # HTTP/2 200 # content-type: text/plain; charset=utf-8 # # 1158201444 ``` -------------------------------- ### Send event with curl to ingest URL Source: https://webhooker.eu/docs/ingest POST an event to the ingest URL with a JSON body. The body is stored exactly as received, any content type is accepted, and the maximum body size is 1 MB. A successful call returns 200 OK with the stored event's ID in request_id. ```bash curl -X POST https://app.webhooker.eu/in/YOUR_SOURCE_TOKEN \ -H "Content-Type: application/json" \ -d '{"event":"order.created","id":"evt_123"}' # HTTP/2 200 # content-type: application/json # # { # "status": "SUCCESS", # "message": "Request received successfully.", # "request_id": "0199a3f2-7c1e-7b4d-9e2a-5f8c1d3b6a47" # } ``` -------------------------------- ### Send a test event with curl Source: https://webhooker.eu/docs/quickstart Use this curl command to send a test event to your Webhooker source. Replace YOUR_SOURCE_TOKEN with your actual token. The command posts a JSON payload with an event type of 'ping'. The expected response is HTTP/2 200 with a JSON body indicating success. ```bash curl -X POST https://app.webhooker.eu/in/YOUR_SOURCE_TOKEN \ -H "Content-Type: application/json" \ -d '{"event":"ping"}' # HTTP/2 200 # {"status":"SUCCESS","message":"Request received successfully.","request_id":"…"} ``` -------------------------------- ### Go (net/http) Webhooker signature verification Source: https://webhooker.eu/docs/verify-signatures Go handler reads the request body with io.ReadAll and verifies HMAC-SHA256 signature using hmac.Equal. Rejects stale deliveries older than 5 minutes. Uses X-Webhooker-Event-Id as a stable idempotency key. ```go package main import ( "crypto/hmac" "crypto/sha256" "encoding/hex" "io" "net/http" "strconv" "time" ) const signingSecret = "whsec_..." // load this from the environment func isValid(r *http.Request, body []byte, tolerance time.Duration) bool { received := r.Header.Get("X-Webhooker-Signature") tsHeader := r.Header.Get("X-Webhooker-Timestamp") ts, err := strconv.ParseInt(tsHeader, 10, 64) if err != nil { return false } // Reject stale deliveries to blunt replay attacks. if time.Since(time.Unix(ts, 0)).Abs() > tolerance { return false } mac := hmac.New(sha256.New, []byte(signingSecret)) mac.Write([]byte(tsHeader + ".")) mac.Write(body) expected := "v1=" + hex.EncodeToString(mac.Sum(nil)) return hmac.Equal([]byte(received), []byte(expected)) } func handler(w http.ResponseWriter, r *http.Request) { body, _ := io.ReadAll(r.Body) if !isValid(r, body, 5*time.Minute) { w.WriteHeader(http.StatusUnauthorized) return } // r.Header.Get("X-Webhooker-Event-Id") is a stable idempotency key. w.WriteHeader(http.StatusOK) } ``` -------------------------------- ### Python (Flask) Webhooker signature verification Source: https://webhooker.eu/docs/verify-signatures Flask view reads raw request bytes with get_data() before parsing. Verifies HMAC-SHA256 signature using hmac.compare_digest and rejects stale deliveries older than 300 seconds. Uses X-Webhooker-Event-Id as an idempotency key. ```python import hmac import hashlib import time from flask import Flask, request app = Flask(__name__) SIGNING_SECRET = "whsec_..." # load this from an environment variable def is_valid(req, tolerance_seconds=300): received = req.headers.get("X-Webhooker-Signature", "") timestamp = req.headers.get("X-Webhooker-Timestamp", "") raw_body = req.get_data() # exact bytes, before any parsing if not timestamp.isdigit() or abs(time.time() - int(timestamp)) > tolerance_seconds: return False signed = timestamp.encode() + b"." + raw_body digest = hmac.new(SIGNING_SECRET.encode(), signed, hashlib.sha256).hexdigest() expected = "v1=" + digest return hmac.compare_digest(received, expected) @app.post("/webhooks") def webhooks(): if not is_valid(request): return "bad signature", 401 # X-Webhooker-Event-Id is stable across retries: use it as an idempotency key. event_id = request.headers.get("X-Webhooker-Event-Id") # ... process the event, then acknowledge ... return "", 200 ``` -------------------------------- ### Meta Event Signature Verification Source: https://webhooker.eu/docs/meta Meta signs events using HMAC-SHA256. The X-Hub-Signature-256 header contains the hex digest, which can be verified using the GitHub authentication preset in Webhooker. ```http POST /in/YOUR_SOURCE_TOKEN Content-Type: application/json X-Hub-Signature-256: sha256=6f1c0e3a5b... {"object":"whatsapp_business_account","entry":[...]} ``` -------------------------------- ### Node.js (Express) Webhooker signature verification Source: https://webhooker.eu/docs/verify-signatures Express middleware captures raw request bytes via express.json verify callback. Verifies HMAC-SHA256 signature with constant-time comparison and rejects stale deliveries older than 300 seconds. Uses X-Webhooker-Event-Id as an idempotency key. ```javascript const crypto = require("crypto"); const express = require("express"); const app = express(); // Capture the exact bytes Webhooker signed. Letting a JSON parser run first // re-serializes the body and the signature will no longer match. app.use(express.json({ verify: (req, res, buf) => { req.rawBody = buf; }, })); const SIGNING_SECRET = process.env.WEBHOOKER_SIGNING_SECRET; // "whsec_..." function isValid(req, toleranceSeconds) { const received = req.get("X-Webhooker-Signature") || ""; const timestamp = req.get("X-Webhooker-Timestamp") || ""; // Reject stale deliveries to blunt replay attacks. const sentAt = Number(timestamp); if (!timestamp || !Number.isFinite(sentAt)) return false; const age = Math.abs(Math.floor(Date.now() / 1000) - sentAt); if (age > toleranceSeconds) return false; const hmac = crypto.createHmac("sha256", SIGNING_SECRET); hmac.update(timestamp + "."); hmac.update(req.rawBody); const expected = "v1=" + hmac.digest("hex"); // Constant-time compare; timingSafeEqual throws on a length mismatch. const a = Buffer.from(received); const b = Buffer.from(expected); return a.length === b.length && crypto.timingSafeEqual(a, b); } app.post("/webhooks", (req, res) => { if (!isValid(req, 300)) return res.status(401).send("bad signature"); // X-Webhooker-Event-Id is stable across retries: use it as an idempotency key. const eventId = req.get("X-Webhooker-Event-Id"); // ... process the event, then acknowledge ... res.sendStatus(200); }); app.listen(3000); ``` -------------------------------- ### 401 Unauthorized response body Source: https://webhooker.eu/docs/ingest Shows the response body when inbound signature verification fails. The event is still stored for audit but is not delivered. The body carries error.code = signature_invalid. ```bash # HTTP/2 401 # content-type: application/json # # { # "error": { # "code": "signature_invalid", # "message": "inbound signature verification failed" # } # } ``` === COMPLETE CONTENT === This response contains all available snippets from this library. No additional content exists. Do not make further requests.