Skip to main content

Documentation Index

Fetch the complete documentation index at: https://appstleinc-aeca3e0a.mintlify.app/llms.txt

Use this file to discover all available pages before exploring further.

Appstle Memberships webhooks deliver real-time HTTP notifications to your server whenever a membership event occurs. Webhooks are powered by Svix — enterprise-grade infrastructure with automatic retries, cryptographic signature verification, and detailed delivery monitoring.

Getting started

1

Open webhook settings

In your Appstle Memberships admin, go to Settings → Webhooks.
2

Add an endpoint

Click Add Endpoint and enter your HTTPS endpoint URL.
3

Select events

Choose which events you want to receive, or subscribe to all events to ensure you never miss one.
4

Save

Your endpoint will start receiving events immediately after saving.
Webhooks are available on paid plans. Contact support@appstle.com to enable access.

How webhooks work

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 them for background processing, then respond immediately
Svix provides automatic retries with exponential backoff, cryptographic signature verification, detailed delivery logs, and the ability to replay events from your dashboard.

Event types

Membership lifecycle events

EventDescription
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 or plan in membership changed
membership.next-order-date-changedNext renewal date rescheduled
membership.billing-interval-changedBilling frequency changed

Billing events

EventDescription
membership.billing-successRenewal payment processed successfully
membership.billing-failureRenewal payment failed

Payload structure

All webhooks share this top-level structure:
{
  "type": "membership.created",
  "data": {}
}
The data field contains the event-specific payload.

Membership contract payload

The lifecycle 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 deliver a full membership contract object:
{
  "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 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
  }
}

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:
ValueMeaning
INVALID_PAYMENT_METHODPayment method is expired or invalid
INSUFFICIENT_FUNDSInsufficient account balance
CARD_DECLINEDCard declined by issuer
AUTHENTICATION_REQUIREDCustomer must re-authenticate their payment method
EXPIRED_PAYMENT_METHODPayment method has expired

Signature verification

Every webhook request is signed by Svix. Always verify the signature before processing the payload — this ensures the request genuinely came from Appstle and has not been tampered with. Svix includes three headers on every request:
HeaderDescription
svix-idUnique message ID — use as an idempotency key
svix-timestampUnix timestamp of when the message was sent
svix-signatureHMAC-SHA256 signature
Find your webhook signing secret in your Appstle dashboard under Settings → Webhooks → [your endpoint].
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');
});
Pass the raw request body to the signature verification function — before JSON parsing. Parsing the body first will cause verification to fail because the byte representation changes.
For Ruby, Go, PHP, Java, and C# examples, see the Svix documentation.

Retry schedule

If your endpoint returns a non-2xx status code or times out, Svix retries with exponential backoff across 5 attempts over 3 days. You can 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 value as an idempotency key to safely deduplicate events in your database before processing.
const messageId = req.headers['svix-id'];

const alreadyProcessed = await db.processedEvents.exists({ id: messageId });
if (alreadyProcessed) {
  return res.status(200).send('OK'); // already handled
}

await db.processedEvents.create({ id: messageId });
// ... process the event

Local development

Use ngrok to expose your local server for webhook testing:
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 are using the correct signing secret from the dashboard.
Endpoint timing outReturn 200 OK immediately, then process the event asynchronously in a background job or queue.
Not receiving eventsConfirm the webhook integration is enabled in Settings and that your endpoint is publicly accessible via HTTPS.
Duplicate eventsUse the svix-id header as a deduplication key before processing.