Agent skill

Appstle Webhooks Skill

Receive and verify Appstle Subscriptions webhooks (delivered via Svix). Use when setting up Appstle webhook handlers, debugging svix-signature verification, or handling subscription events like subscription.created, subscription.cancelled, subscription.billing-success, and subscription.billing-failure.

Install this skill

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


Appstle Subscriptions (a Shopify subscriptions app) sends webhooks signed with Svix's scheme. Every delivery is an HTTP POST with a JSON body shaped { "type": "subscription.created", "data": { ... } } and three signature headers: svix-id, svix-timestamp, and svix-signature. Webhooks are included in Appstle's Enterprise and Enterprise Plus plans.

When to Use This Skill

  • How do I receive Appstle webhooks?
  • How do I verify Appstle webhook signatures (svix-signature)?
  • How do I handle subscription.created or subscription.cancelled events?
  • How do I react to failed payments (subscription.billing-failure)?
  • Why is my Appstle webhook signature verification failing, or failing only on 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. Appstle's docs show a hand-written verifier that also rejects a svix-timestamp more than five minutes from your clock. The official svix library implements the same scheme and check, and compares in constant time.

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

const wh = new Webhook(process.env.APPSTLE_WEBHOOK_SECRET); // "whsec_..."
// rawBody: the raw Buffer/string, never re-serialized JSON
wh.verify(rawBody, {
  'svix-id': req.headers['svix-id'],
  'svix-timestamp': req.headers['svix-timestamp'],
  'svix-signature': req.headers['svix-signature'],
}); // throws WebhookVerificationError on a bad signature or stale timestamp
const event = JSON.parse(rawBody.toString('utf8')); // parse only after verify passes
from svix.webhooks import Webhook, WebhookVerificationError

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

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

Timestamp window and Hookdeck retries

The svix (and standardwebhooks) libraries always enforce a fixed 5-minute timestamp window and offer no option to change it. Appstle's own retries carry a new timestamp, but Hookdeck retries and replays resend the original svix-timestamp, so a retry or replay that arrives more than 5 minutes after the event fails library verification even though the signature is valid. If you receive Appstle webhooks through Hookdeck, or otherwise need to accept late retries, use the manual verifier in references/verification.md: HMAC-SHA256 over {id}.{timestamp}.{raw body}, key = base64-decode of the secret after whsec_, compare every space-separated v1,<base64> entry in constant time. It lets you drop the window or widen it.

Common Event Types

The type field of the body carries the event name. Appstle documents these:

EventTriggered Whendata
subscription.createdA new subscription is createdSubscription contract
subscription.updatedSubscription details changeSubscription contract
subscription.activatedA subscription is activatedSubscription contract
subscription.pausedA subscription is pausedSubscription contract
subscription.cancelledA subscription is cancelledSubscription contract
subscription.next-order-date-changedThe next order date is changedSubscription contract
subscription.billing-interval-changedThe billing frequency changesSubscription contract
subscription.billing-successA payment is processedBilling attempt
subscription.billing-failureA payment try fails (sent after every failed try)Billing attempt
subscription.billing-skippedA billing cycle is skippedBilling attempt
subscription.upcoming-order-notificationA queued order comes inside its notice periodBilling attempt with contract fields

Always branch on type and ignore types you don't handle: the list can grow. See references/overview.md for payload fields.

Environment Variables

# More → Webhook Integration → Launch webhook portal → your endpoint (starts with whsec_)
APPSTLE_WEBHOOK_SECRET=whsec_xxxxx

Local Development

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

Register the tunnel URL in the webhook portal (More → Webhook Integration → Launch webhook portal), then open the endpoint and send it an example event.

Reference Materials


Repository

hookdeck/webhook-skills

v0.1.0 · MIT · Updated Nov 5, 2026

View on GitHub →