Agent skill

Stitch Webhooks Skill

Receive and verify Stitch (stitch.money) payment webhooks delivered via Svix. Use when setting up a Stitch webhook handler, debugging svix-id / svix-timestamp / svix-signature verification, or handling Stitch payment, payment.confirmation, transaction, payment-consent-request, collection, refund, and disbursement status events.

Install this skill

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


Stitch (stitch.money, the South African payments API, not Stitch Data the ETL tool) delivers webhooks through Svix. Each webhook subscription has its own signing secret (whsec_...), returned when you create the subscription via the GraphQL API or shown in the Svix consumer portal.

When to Use This Skill

  • How do I receive Stitch webhooks?
  • How do I verify Stitch webhook signatures (svix-signature)?
  • How do I handle Stitch payment, refund, or disbursement status updates?
  • Why is my Stitch webhook signature verification failing?
  • How do I subscribe to Stitch webhooks with clientWebhookAdd?
  • How do I migrate from Stitch legacy webhooks to Svix webhooks?

Verification (core)

Every delivery carries three headers:

svix-id: msg_2b1c...                 # unique message id (use for idempotency)
svix-timestamp: 1614265330           # Unix seconds
svix-signature: v1,g0hM9SsE... v2,...  # space-delimited "version,signature" entries

The signed content is ${svix-id}.${svix-timestamp}.${raw_body}, signed with HMAC-SHA256 using the base64-decoded bytes of the secret after the whsec_ prefix, base64-encoded. Compare only the v1, entries. Stitch recommends the official svix library (Node and Python), which also handles the 5-minute timestamp tolerance, rotation, and constant-time comparison:

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

const wh = new Webhook(process.env.STITCH_WEBHOOK_SECRET); // "whsec_..."
wh.verify(rawBody, {                 // rawBody: raw Buffer/string, NOT parsed JSON
  'svix-id': req.headers['svix-id'],
  'svix-timestamp': req.headers['svix-timestamp'],
  'svix-signature': req.headers['svix-signature'],
}); // throws WebhookVerificationError on bad signature / stale timestamp
const event = JSON.parse(rawBody.toString('utf8')); // parse only after it passes

Python is the same: from svix.webhooks import Webhook and Webhook(secret).verify(raw_body_bytes, headers). For a manual (no SDK) implementation, see references/verification.md.

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

Common Event Types

You subscribe with filterTypes on clientWebhookAdd (or in the Svix portal):

Event typeProductSent when
paymentPay by Bank, Capitec Pay, Card (once-off), Manual EFT, CashPayment request status changes (Completed, Cancelled, Expired)
payment.confirmationSame, with a Stitch intermediary accountFunds received (PaymentReceived, PaymentUnsettled)
payment-consent-requestCard, Capitec Pay Recurring, DebiCheckConsent status changes (GRANTED, FAILED, REVOKED, ...)
transactionCard, Capitec Pay Recurring, DebiCheckTransaction reaches SUCCESS, FAILURE, CANCELLED
collectionBatch collections (subscriptions)A collection reaches completed, failed, cancelled
refundRefundsRefund status changes (Submitted, Completed, Error, Paused)
disbursementDisbursementsDisbursement status changes (Submitted, Completed, Error, Paused, Cancelled, Reversed)

Payload gotcha: payment-consent-request, transaction, and collection payloads have a top-level type field. payment, payment.confirmation, refund, and disbursement payloads are GraphQL-shaped ({"data": {"client": {"paymentInitiationRequests" | "refunds" | "disbursements": {"node": {...}}}}}) with no type field, so dispatch on the data.client key. payment and payment.confirmation share the same shape; read state.__typename and paymentConfirmation.__typename on the node. The examples include a getEventType() helper that handles both shapes.

Deprecated types (direct-deposit, payment-initiation, payment-initiation.confirmation) still exist; see references/overview.md.

Environment Variables

# Svix signing secret for your Stitch webhook subscription (starts with whsec_)
STITCH_WEBHOOK_SECRET=whsec_xxxxx

Local Development

For local webhook testing, run the Hookdeck CLI via npx (no install required):

npx hookdeck-cli listen 3000 stitch --path /webhooks/stitch

No account required. Register the printed URL as your subscription url.

Reference Materials


Repository

hookdeck/webhook-skills

v0.1.0 · MIT · Updated Oct 28, 2026

View on GitHub →