Agent skill

Goody Webhooks Skill

Receive and verify Goody webhooks (corporate gifting API, delivered via Svix). Use when setting up Goody webhook handlers, debugging svix-signature verification, or handling order events like order.created, order.gift_accepted, order.shipped, and order_batch.completed.

Install this skill

npx skills add hookdeck/webhook-skills --skill goody-webhooks


Goody (Automation API and Commerce API) sends webhooks through Svix, which implements Standard Webhooks. Every delivery is an HTTP POST with a JSON body and three signature headers: svix-id, svix-timestamp, and svix-signature. The body is { "event_type", "id", "data" }: data is the order, or the order batch for order_batch.*.

When to Use This Skill

  • How do I receive Goody webhooks?
  • How do I verify Goody webhook signatures (svix-signature)?
  • How do I handle order.created or order.gift_accepted events?
  • How do I react to order.shipped, order.delivered, or order_batch.completed?
  • Why is my Goody webhook signature verification failing, or rejecting Hookdeck retries?

Verification (core)

Signed content is {svix-id}.{svix-timestamp}.{raw body}. Svix signs it with HMAC-SHA256 using the base64-decoded secret (the part after whsec_), base64-encodes the result, and sends one or more space-separated v1,<signature> entries. Use the official svix library, which compares in constant time. Read the svix-* headers, and when svix-id is absent fall back to the webhook-* headers (webhook-id, webhook-timestamp, webhook-signature), as Hookdeck does.

import { Webhook } from 'svix'; // svix 2.x is ESM-only

const prefix = req.headers['svix-id'] ? 'svix' : 'webhook';
const id = req.headers[`${prefix}-id`];
const timestamp = req.headers[`${prefix}-timestamp`];
const signature = req.headers[`${prefix}-signature`];
// rawBody: the raw Buffer/string, never re-serialized JSON
new Webhook(process.env.GOODY_WEBHOOK_SECRET).verify(rawBody, {
  'svix-id': id, 'svix-timestamp': timestamp, 'svix-signature': signature,
  'webhook-id': id, 'webhook-timestamp': timestamp, 'webhook-signature': signature,
}); // throws on a bad signature or a timestamp outside the window
const event = JSON.parse(rawBody.toString('utf8')); // parse only after verify passes
from svix.webhooks import Webhook, WebhookVerificationError

wh = Webhook(os.environ["GOODY_WEBHOOK_SECRET"])  # "whsec_..."
wh.verify(raw_body, {"svix-id": id, "svix-timestamp": ts, "svix-signature": sig,
                     "webhook-id": id, "webhook-timestamp": ts, "webhook-signature": sig})

For complete handlers with tests, see examples/express/, examples/nextjs/, examples/fastapi/.

The secret is the Signing Secret on the webhook's configuration page in Goody and starts with whsec_. Each webhook has its own, and sandbox and production are separate. Keep the full whsec_... value in GOODY_WEBHOOK_SECRET. Handlers fail closed: with the variable unset they return 500 instead of accepting anything.

Hookdeck retries and replays hit the 5-minute window

The svix and standardwebhooks libraries always enforce a fixed 5-minute timestamp window, and it can't be configured. Hookdeck retries and replays resend the original svix-timestamp, so the library rejects any retry or replay that happens more than 5 minutes after the original delivery. If you verify behind Hookdeck (or replay old events), use the manual verifier in references/verification.md, where the window can be dropped or widened. Deduplicate on svix-id instead of relying on the timestamp.

Common Event Types

10 events are documented (the list may not be exhaustive). The name is event_type in the JSON body, and the order or order batch is in data. The examples handle all of them:

EventTriggered When
order_batch.createdAn order batch is created (may still be sending or scheduled)
order_batch.completedAn order batch is fully processed and all orders exist
order.createdAn order is created and ready to be opened (individual_gift_link is set)
order.gift_openedA gift is first opened
order.gift_acceptedThe recipient accepts a gift; final costs are calculated
order.thank_you_note_addedThe recipient adds a thank you note
order.shippedThe order ships (first shipment in transit)
order.deliveredThe order is delivered (all shipments delivered)
order.canceledThe order is canceled
order.refundedThe order is refunded

Goody's example payloads for the batch events show gift_batch.created and gift_batch.completed; the examples accept both spellings. See references/overview.md for the payload shape and batch timing.

Environment Variables

# Signing Secret of the Goody webhook (Svix format, starts with whsec_)
GOODY_WEBHOOK_SECRET=whsec_xxxxx

Local Development

# Start tunnel (no account needed)
npx hookdeck-cli listen 3000 goody --path /webhooks/goody

Register the tunnel URL as the Endpoint URL of a webhook under Organization > Automation API (or Commerce API) in Goody, then create a test order in the sandbox to see events arrive.

Reference Materials


Repository

hookdeck/webhook-skills

v0.1.0 · MIT · Updated Nov 12, 2026

View on GitHub →