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.createdorsubscription.cancelledevents? - 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:
| Event | Triggered When | data |
|---|---|---|
subscription.created | A new subscription is created | Subscription contract |
subscription.updated | Subscription details change | Subscription contract |
subscription.activated | A subscription is activated | Subscription contract |
subscription.paused | A subscription is paused | Subscription contract |
subscription.cancelled | A subscription is cancelled | Subscription contract |
subscription.next-order-date-changed | The next order date is changed | Subscription contract |
subscription.billing-interval-changed | The billing frequency changes | Subscription contract |
subscription.billing-success | A payment is processed | Billing attempt |
subscription.billing-failure | A payment try fails (sent after every failed try) | Billing attempt |
subscription.billing-skipped | A billing cycle is skipped | Billing attempt |
subscription.upcoming-order-notification | A queued order comes inside its notice period | Billing 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
- references/overview.md - Appstle webhook concepts, event list, payloads, retries
- references/setup.md - Register an endpoint and get the signing secret
- references/verification.md - SDK and manual verification, timestamp window, gotchas