Receive real-time notifications for grant lifecycle events.
Grantex can POST a signed JSON payload to your server whenever key events occur — grant created, grant revoked, or token issued. Use webhooks to keep your application in sync with the authorization lifecycle without polling.
Every request carries a timestamped signature over <timestamp>.<raw body>:
X-Grantex-Timestamp: <unix seconds>X-Grantex-Signature-V2: sha256=<hex>X-Grantex-Signature: sha256=<hex> # legacy, see below
Verify against X-Grantex-Signature-V2 and reject deliveries older than your
tolerance (300 seconds by default). Both halves matter: the timestamp is inside
the signed material, so it cannot be rewritten, and without it a captured
delivery would stay valid forever.Each delivery attempt is signed at the moment it is sent, so a retry arriving
after backoff carries a fresh timestamp rather than the time the event was
queued.Always verify against the raw request body. A body that has been parsed and
re-serialized will not hash to the same value.TypeScript:
import { verifyWebhook } from '@grantex/sdk';app.post('/webhooks/grantex', (req, res) => { const rawBody = req.rawBody; // raw string/Buffer, not parsed JSON const ok = verifyWebhook({ payload: rawBody, signature: req.headers['x-grantex-signature-v2'] as string, timestamp: req.headers['x-grantex-timestamp'] as string, secret: process.env.GRANTEX_WEBHOOK_SECRET!, }); if (!ok) return res.status(401).send('Invalid signature'); const event = JSON.parse(rawBody); console.log('Received event:', event.type); res.status(200).send();});
Python:
from grantex import verify_webhookimport os@app.route('/webhooks/grantex', methods=['POST'])def handle_webhook(): raw_body = request.get_data(as_text=True) ok = verify_webhook( raw_body, request.headers.get('X-Grantex-Signature-V2', ''), request.headers.get('X-Grantex-Timestamp', ''), os.environ['GRANTEX_WEBHOOK_SECRET'], ) if not ok: return 'Invalid signature', 401 event = request.get_json() print('Received event:', event['type']) return '', 200
The original header signs the body alone. Because it commits to nothing
time-bound, a delivery captured once stays valid forever and can be replayed at
any time. It is still sent so existing receivers keep working, and
verifyWebhookSignature / verify_webhook_signature /
VerifyWebhookSignature still verify it — but they are deprecated. Move to the
timestamped header; the legacy one will be withdrawn.