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-Signature header?
  • Why is my Infinite Giving webhook signature verification failing?
  • How do I handle donation:created and donation:updated events?
  • 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 use express.raw({ type: '*/*' }).
  • Header value as-is. Use the X-IG-Timestamp value exactly as received (Unix seconds); don't trim or reformat it.
  • Prefix optional. Accept X-IG-Signature with or without the sha256= 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_GIVING_WEBHOOK_TOLERANCE_SECONDS (unset or 0 = off) that checks X-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:

HeaderDescription
X-IG-Signaturesha256=<hex> HMAC-SHA256 signature
X-IG-TimestampUnix timestamp (seconds) used in the signature
X-IG-EventEvent type, e.g. donation:created
X-IG-Webhook-IdUnique 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

EventTriggered WhenCommon Use Cases
donation:createdA donation record is createdRecord a pledged gift, notify the nonprofit
donation:updatedStatus 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


Repository

hookdeck/webhook-skills

v0.1.0 · MIT · Updated Nov 10, 2026

View on GitHub →