Agent skill
Infinite Giving Webhooks Skill
Receive and verify Infinite Giving webhooks. Use when setting up Infinite Giving partner webhook handlers, debugging X-IG-Signature verification, or handling donation:created and donation:updated events for stock, crypto, and DAF donations.
Install this skill
npx skills add hookdeck/webhook-skills --skill infinite-giving-webhooks
Infinite Giving (infinitegiving.com) is a non-cash donation platform (stock, crypto, DAF) for nonprofits. Integration partners receive a webhook whenever a donation is created or updated for a connected organization.
Infinite Giving webhooks are for integration partners. Paste the signing secret Infinite Giving gives you at partner kickoff.
When to Use This Skill
- How do I receive Infinite Giving webhooks?
- How do I verify the
X-IG-Signatureheader? - Why is my Infinite Giving webhook signature verification failing?
- How do I handle
donation:createdanddonation:updatedevents? - How do I detect that a stock or crypto donation completed?
Verification (core)
Infinite Giving signs {X-IG-Timestamp}.{raw body} with HMAC-SHA256 and sends the lowercase hex digest in X-IG-Signature as sha256=<hex>. The key is your signing secret used as plain UTF-8 (not hex- or base64-decoded). Verify the raw bytes before parsing any JSON.
const crypto = require('crypto');
function verifyInfiniteGiving(rawBody, timestamp, signature, secret) { // rawBody: Buffer
if (!timestamp || !signature || !secret) return false;
const hex = signature.replace(/^sha256=/, '').toLowerCase(); // prefix optional
if (!/^[0-9a-f]{64}$/.test(hex)) return false; // validate before decoding
const expected = crypto.createHmac('sha256', secret)
.update(Buffer.concat([Buffer.from(timestamp, 'latin1'), Buffer.from('.'), rawBody]))
.digest();
return crypto.timingSafeEqual(expected, Buffer.from(hex, 'hex'));
}
// Missing, malformed, or mismatched signature -> respond 401
import hashlib, hmac, re
def verify_infinite_giving(raw_body: bytes, timestamp: str, signature: str, secret: str) -> bool:
if not timestamp or not signature or not secret:
return False
digest = signature.removeprefix("sha256=").lower()
if not re.fullmatch(r"[0-9a-f]{64}", digest):
return False
signed = timestamp.encode("latin-1") + b"." + raw_body
expected = hmac.new(secret.encode(), signed, hashlib.sha256).digest()
return hmac.compare_digest(expected, bytes.fromhex(digest))
Key points:
- Raw body. Never
JSON.parse/JSON.stringify(or decode and re-encode) before verifying; the signature covers the exact bytes received. In Express useexpress.raw({ type: '*/*' }). - Header value as-is. Use the
X-IG-Timestampvalue exactly as received (Unix seconds); don't trim or reformat it. - Prefix optional. Accept
X-IG-Signaturewith or without thesha256=prefix; compare hex case-insensitively. - No timestamp window by default. Retries and Hookdeck replays resend the original headers, so rejecting old timestamps would drop legitimate deliveries. The examples offer an opt-in
INFINITE_(unset orGIVING_ WEBHOOK_ TOLERANCE_ SECONDS 0= off) that checksX-IG-Timestamp(Unix seconds) against the clock.
For complete handlers with tests, see examples/express/, examples/nextjs/, examples/fastapi/.
Request Format
POST with a JSON envelope. Dispatch on the body's event field:
| Header | Description |
|---|---|
X-IG-Signature | sha256=<hex> HMAC-SHA256 signature |
X-IG-Timestamp | Unix timestamp (seconds) used in the signature |
X-IG-Event | Event type, e.g. donation:created |
X-IG-Webhook-Id | Unique delivery id (same as the envelope id); use it to dedupe |
{ "id": "A1B2C3D4E", "event": "donation:created", "created_at": "2026-06-23T21:00:00.000Z", "data": { "id": "DON1234567", "object": "donation", "status": "pledged" } }
Common Event Types
| Event | Triggered When | Common Use Cases |
|---|---|---|
donation:created | A donation record is created | Record a pledged gift, notify the nonprofit |
donation:updated | Status or material fields change (processing, complete, failed, canceled) | Mark a gift complete, reconcile the sale proceeds |
There is no donation:completed event: completion arrives as donation:updated with data.status set to "complete". Full details in references/overview.md.
Delivery
Up to 5 attempts: the first delivery plus four retries about 1m, 5m, 30m, and 2h apart. The docs state no timeout, so respond with a 2xx quickly and process asynchronously. Make handlers idempotent (key on X-IG-Webhook-Id / envelope id).
Environment Variables
INFINITE_GIVING_WEBHOOK_SECRET=your_partner_signing_secret # given at partner kickoff, used as UTF-8
INFINITE_GIVING_WEBHOOK_TOLERANCE_SECONDS=0 # optional, 0/unset = no timestamp check
Local Development
npx hookdeck-cli listen 3000 infinite-giving --path /webhooks/infinite-giving
No account required: the CLI creates a guest account, a public tunnel, and a web UI for inspecting requests.
Reference Materials
- references/overview.md - Events, headers, and payload structure
- references/setup.md - Partner onboarding and signing secret
- references/verification.md - Signature scheme details and gotchas