Skip to content
Last updated

Webhooks

Receive real-time HTTP notifications when membership events occur. Appstle Memberships webhooks are powered by Svix — enterprise-grade webhook infrastructure with automatic retries, signature verification, and delivery monitoring.

Getting Started

  1. In your Appstle Memberships admin, go to Settings → Webhooks
  2. Click Add Endpoint and enter your HTTPS endpoint URL
  3. Select which events you want to receive (or subscribe to all)
  4. Save — your endpoint will start receiving events immediately

ℹ️ Requires Webhook Access: Webhooks are available on paid plans. Contact support@appstle.com to enable.

How It Works

Webhooks are HTTP POST requests sent to your endpoint whenever a membership event occurs. Your endpoint must:

  • Be publicly accessible via HTTPS
  • Return a 2xx status code within the timeout window
  • Process events asynchronously (queue for background processing)

Powered by Svix:

  • ✅ Automatic retries with exponential backoff
  • ✅ Cryptographic signature verification
  • ✅ Detailed delivery logs and replay
  • ✅ Developer dashboard for monitoring

Event Types

Event TypeDescription
membership.createdNew membership contract created
membership.updatedMembership details modified
membership.activatedMembership activated
membership.pausedMembership paused
membership.cancelledMembership cancelled
membership.expiredMembership expired at end of term
membership.swap-productProduct/plan in membership changed
membership.next-order-date-changedNext renewal date rescheduled
membership.billing-interval-changedBilling frequency changed
membership.billing-successRenewal payment processed successfully
membership.billing-failureRenewal payment failed

Payload Structure

All webhooks follow this standard structure:

{
  "type": "membership.created",
  "data": {
    // Event-specific payload
  }
}

Membership Contract Events

Events membership.created, membership.updated, membership.activated, membership.paused, membership.cancelled, membership.expired, membership.swap-product, membership.next-order-date-changed, and membership.billing-interval-changed all send a full membership contract payload.

📄 Membership Contract Payload Example
{
  "type": "membership.created",
  "data": {
    "id": "gid://shopify/SubscriptionContract/12345",
    "createdAt": "2026-01-15T10:30:00Z",
    "updatedAt": "2026-01-15T10:30:00Z",
    "nextBillingDate": "2026-02-15",
    "status": "ACTIVE",
    "billingPolicy": {
      "interval": "MONTH",
      "intervalCount": 1,
      "anchors": [],
      "maxCycles": 12,
      "minCycles": 1
    },
    "deliveryPolicy": {
      "interval": "MONTH",
      "intervalCount": 1,
      "anchors": []
    },
    "lines": {
      "nodes": [
        {
          "id": "gid://shopify/SubscriptionLine/67890",
          "productId": "gid://shopify/Product/11111",
          "variantId": "gid://shopify/ProductVariant/22222",
          "sellingPlanId": "gid://shopify/SellingPlan/33333",
          "sellingPlanName": "Gold Member — Monthly",
          "title": "Gold Membership",
          "variantTitle": "Monthly",
          "quantity": 1,
          "currentPrice": {
            "amount": "29.00",
            "currencyCode": "USD"
          }
        }
      ]
    },
    "customer": {
      "id": "gid://shopify/Customer/55555",
      "email": "member@example.com",
      "displayName": "Jane Doe",
      "firstName": "Jane",
      "lastName": "Doe",
      "phone": "+1-555-123-4567"
    },
    "originOrder": {
      "id": "gid://shopify/Order/77777",
      "name": "#1001"
    },
    "deliveryPrice": {
      "amount": "0.00",
      "currencyCode": "USD"
    },
    "lastPaymentStatus": "SUCCEEDED",
    "note": null,
    "customAttributes": []
  }
}

Billing Events

membership.billing-success and membership.billing-failure send billing attempt data.

✅ membership.billing-success Payload
{
  "type": "membership.billing-success",
  "data": {
    "id": 98765,
    "shop": "example-store.myshopify.com",
    "billingAttemptId": "gid://shopify/SubscriptionBillingAttempt/99999",
    "contractId": 12345,
    "status": "SUCCESS",
    "billingDate": "2026-02-15T00:00:00Z",
    "attemptTime": "2026-02-15T10:30:00Z",
    "attemptCount": 1,
    "orderId": 77778,
    "orderName": "#1002",
    "orderAmount": 29.00,
    "retryingNeeded": false,
    "billingAttemptResponseMessage": null
  }
}
❌ membership.billing-failure Payload
{
  "type": "membership.billing-failure",
  "data": {
    "id": 98766,
    "shop": "example-store.myshopify.com",
    "billingAttemptId": "gid://shopify/SubscriptionBillingAttempt/99998",
    "contractId": 12345,
    "status": "FAILURE",
    "billingDate": "2026-03-15T00:00:00Z",
    "attemptTime": "2026-03-15T10:30:00Z",
    "attemptCount": 1,
    "orderId": null,
    "orderName": null,
    "orderAmount": null,
    "retryingNeeded": true,
    "billingAttemptResponseMessage": "INVALID_PAYMENT_METHOD: The payment method is invalid."
  }
}

Common billingAttemptResponseMessage values:

  • INVALID_PAYMENT_METHOD — Payment method is expired or invalid
  • INSUFFICIENT_FUNDS — Insufficient account balance
  • CARD_DECLINED — Card declined by issuer
  • AUTHENTICATION_REQUIRED — Customer must re-authenticate payment
  • EXPIRED_PAYMENT_METHOD — Payment method has expired

Signature Verification

Every webhook request is signed by Svix. Always verify the signature before processing the payload.

Svix includes these headers on every request:

  • svix-id — Unique message ID (use for idempotency)
  • svix-timestamp — Unix timestamp of when the message was sent
  • svix-signature — HMAC-SHA256 signature

Find your webhook signing secret in your Appstle dashboard under Settings → Webhooks → [your endpoint].

Node.js

const { Webhook } = require('svix');

const secret = 'whsec_your_signing_secret';

app.post('/webhooks/appstle-memberships', express.raw({ type: 'application/json' }), (req, res) => {
  const wh = new Webhook(secret);
  let event;

  try {
    event = wh.verify(req.body, {
      'svix-id': req.headers['svix-id'],
      'svix-timestamp': req.headers['svix-timestamp'],
      'svix-signature': req.headers['svix-signature'],
    });
  } catch (err) {
    return res.status(400).send('Webhook signature verification failed');
  }

  switch (event.type) {
    case 'membership.created':
      // Handle new membership
      break;
    case 'membership.billing-failure':
      // Trigger dunning flow
      break;
    case 'membership.cancelled':
      // Revoke member access
      break;
  }

  res.status(200).send('OK');
});

Python

from svix.webhooks import Webhook, WebhookVerificationError

secret = "whsec_your_signing_secret"

@app.route('/webhooks/appstle-memberships', methods=['POST'])
def webhook():
    headers = {
        "svix-id": request.headers.get("svix-id"),
        "svix-timestamp": request.headers.get("svix-timestamp"),
        "svix-signature": request.headers.get("svix-signature"),
    }
    try:
        wh = Webhook(secret)
        event = wh.verify(request.data, headers)
    except WebhookVerificationError:
        return "Verification failed", 400

    if event["type"] == "membership.billing-failure":
        # trigger dunning logic
        pass

    return "OK", 200

See Svix docs for Ruby, Go, PHP, Java, and C# examples.

Retry Schedule

If your endpoint returns a non-2xx status or times out, Svix retries with exponential backoff across 5 attempts over 3 days. View delivery history and manually replay events from your Appstle dashboard under Settings → Webhooks → Message Logs.

Idempotency

Webhooks can be delivered more than once. Use the svix-id header as an idempotency key to safely deduplicate events in your database.

Local Development

Use ngrok to expose your local server:

ngrok http 3000
# Then add https://your-id.ngrok.io/webhooks/appstle-memberships as your endpoint

Troubleshooting

IssueSolution
Signature verification failsUse the raw request body (before JSON parsing). Verify you're using the correct secret from the dashboard.
Endpoint timing outReturn 200 OK immediately, then process async.
Not receiving eventsConfirm the webhook integration is enabled in Settings and your endpoint is publicly accessible.
Duplicate eventsUse svix-id header for deduplication.

Need help? Contact support@appstle.com