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.

Webhooks let you receive real-time HTTP notifications when subscription events happen in your Appstle account. Instead of polling the API, you register an endpoint URL and Appstle sends an HTTP POST request to it whenever an event occurs. Appstle webhooks are powered by Svix, which provides automatic retries with exponential backoff, cryptographic signature verification, and detailed delivery logs.

Getting started

1

Configure your endpoint

Log in to your Appstle dashboard, go to Settings → Webhooks, add your webhook endpoint URL, and select which events you want to receive.
2

Return 2xx quickly

Your endpoint must return a 2xx status code (e.g. 200 OK) to acknowledge receipt. Process the event asynchronously in a background job — slow responses will time out and trigger retries.
3

Verify signatures

Every webhook request includes Svix signature headers. Always verify the signature before processing to ensure the request is authentic. See Signature verification below.

Event types

Event typeDescriptionPayload type
subscription.createdNew subscription createdSubscription contract
subscription.updatedSubscription details updatedSubscription contract
subscription.activatedSubscription activatedSubscription contract
subscription.pausedSubscription pausedSubscription contract
subscription.cancelledSubscription cancelledSubscription contract
subscription.next-order-date-changedNext order date modifiedSubscription contract
subscription.billing-interval-changedBilling frequency changedSubscription contract
subscription.billing-successPayment processed successfullyBilling attempt
subscription.billing-failurePayment failedBilling attempt
subscription.billing-skippedBilling cycle skippedBilling attempt
subscription.upcoming-order-notificationUpcoming order reminder sentBilling attempt

Payload structure

All webhooks use this envelope:
{
  "type": "subscription.created",
  "data": {
    // Event-specific payload
  }
}

Subscription contract payloads

The following events carry a full subscription contract in data: subscription.created, subscription.updated, subscription.activated, subscription.paused, subscription.cancelled, subscription.next-order-date-changed, subscription.billing-interval-changed
{
  "type": "subscription.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",
    "deliveryPrice": {
      "amount": "5.00",
      "currencyCode": "USD"
    },
    "lastPaymentStatus": "SUCCEEDED",
    "billingPolicy": {
      "interval": "MONTH",
      "intervalCount": 1,
      "anchors": [{ "type": "MONTHDAY", "day": 15, "month": null, "cutoffDay": null }],
      "maxCycles": null,
      "minCycles": null
    },
    "deliveryPolicy": {
      "interval": "MONTH",
      "intervalCount": 1,
      "anchors": [{ "type": "MONTHDAY", "day": 15, "month": null, "cutoffDay": null }]
    },
    "lines": {
      "nodes": [
        {
          "id": "gid://shopify/SubscriptionLine/67890",
          "productId": "gid://shopify/Product/11111",
          "variantId": "gid://shopify/ProductVariant/22222",
          "sellingPlanId": "gid://shopify/SellingPlan/33333",
          "sellingPlanName": "Subscribe & Save 10%",
          "title": "Premium Coffee Beans",
          "variantTitle": "1lb / Medium Roast",
          "sku": "COFFEE-1LB-MED",
          "quantity": 2,
          "taxable": true,
          "currentPrice": { "amount": "27.00", "currencyCode": "USD" },
          "lineDiscountedPrice": { "amount": "24.30", "currencyCode": "USD" },
          "pricingPolicy": {
            "basePrice": { "amount": "30.00", "currencyCode": "USD" },
            "cycleDiscounts": [
              {
                "afterCycle": 0,
                "adjustmentType": "PERCENTAGE",
                "adjustmentValue": { "percentage": 10.0 },
                "computedPrice": { "amount": "27.00", "currencyCode": "USD" }
              }
            ]
          }
        }
      ]
    },
    "customer": {
      "id": "gid://shopify/Customer/55555",
      "email": "customer@example.com",
      "displayName": "John Doe",
      "firstName": "John",
      "lastName": "Doe",
      "phone": "+1-555-123-4567"
    },
    "customerPaymentMethod": {
      "id": "gid://shopify/CustomerPaymentMethod/66666",
      "instrument": {
        "__typename": "CustomerCreditCard",
        "brand": "VISA",
        "expiresSoon": false,
        "expiryMonth": 12,
        "expiryYear": 2028,
        "lastDigits": "4242",
        "maskedNumber": "•••• •••• •••• 4242",
        "name": "John Doe"
      }
    },
    "deliveryMethod": {
      "__typename": "SubscriptionDeliveryMethodShipping",
      "address": {
        "firstName": "John",
        "lastName": "Doe",
        "address1": "123 Main Street",
        "city": "San Francisco",
        "province": "California",
        "country": "United States",
        "zip": "94102"
      },
      "shippingOption": {
        "title": "Standard Shipping",
        "code": "STANDARD"
      }
    }
  }
}

Subscription contract fields

FieldTypeDescription
idStringShopify GraphQL ID of the subscription contract
statusEnumACTIVE, PAUSED, CANCELLED, EXPIRED, or FAILED
nextBillingDateDateISO 8601 date of next billing (YYYY-MM-DD)
createdAtDateTimeISO 8601 timestamp when subscription was created
updatedAtDateTimeISO 8601 timestamp of last update
lastPaymentStatusEnumSUCCEEDED, FAILED, or null
billingPolicy.intervalEnumDAY, WEEK, MONTH, or YEAR
billingPolicy.intervalCountIntegerNumber of intervals between billings
billingPolicy.maxCyclesIntegerMaximum billing cycles (null = unlimited)
billingPolicy.minCyclesIntegerMinimum billing cycles before cancellation is allowed
lines.nodesArraySubscription line items
customerObjectCustomer details
customerPaymentMethodObjectPayment method (credit card, PayPal, or Shop Pay)
deliveryMethodObjectDelivery method (shipping, local delivery, or pickup)
discounts.nodesArrayApplied discounts
noteStringInternal subscription note
customAttributesArrayCustom key-value attributes

Billing attempt payloads

The following events carry billing attempt data: subscription.billing-success, subscription.billing-failure, subscription.billing-skipped, subscription.upcoming-order-notification
{
  "type": "subscription.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,
    "graphOrderId": "gid://shopify/Order/77777",
    "orderId": 77777,
    "orderName": "#1002",
    "orderAmount": 59.30,
    "retryingNeeded": false,
    "upcomingOrderEmailSentStatus": "SENT"
  }
}

Billing attempt fields

FieldTypeDescription
idLongUnique identifier for this billing attempt record
shopStringShopify store domain
billingAttemptIdStringShopify GraphQL billing attempt ID
contractIdLongRelated subscription contract ID (numeric, no gid:// prefix)
statusEnumSUCCESS, FAILURE, SKIPPED, or PENDING
billingDateDateTimeISO 8601 scheduled billing date
attemptTimeDateTimeISO 8601 actual attempt timestamp
attemptCountIntegerNumber of billing attempts for this cycle
graphOrderIdStringShopify GraphQL order ID (if successful)
orderIdLongShopify numeric order ID (if successful)
orderNameStringOrder name like #1234 (if successful)
orderAmountDoubleOrder total in shop currency
retryingNeededBooleanWhether automatic retry is scheduled
billingAttemptResponseMessageStringError message if failed/skipped, null if successful
inventorySkippedAttemptCountIntegerCount of skips due to inventory issues
partialLinesSkippedEnumReason for partial line skipping: OUT_OF_STOCK, PRICE_CHANGE, etc.

Signature verification

Every webhook request is signed by Svix. You must verify the signature before processing to ensure the request is authentic and has not been tampered with. Svix adds three headers to every request:
HeaderDescription
svix-idUnique message ID — use this for idempotency
svix-timestampUnix timestamp when the message was sent
svix-signatureCryptographic signature
Find your webhook signing secret in Settings → Webhooks in the Appstle dashboard.
const { Webhook } = require('svix');

const secret = 'whsec_your_webhook_signing_secret';

app.post('/webhooks/appstle', (req, res) => {
  const payload = JSON.stringify(req.body);
  const headers = {
    'svix-id': req.headers['svix-id'],
    'svix-timestamp': req.headers['svix-timestamp'],
    'svix-signature': req.headers['svix-signature'],
  };

  const wh = new Webhook(secret);
  let event;

  try {
    event = wh.verify(payload, headers);
  } catch (err) {
    return res.status(400).send('Webhook signature verification failed');
  }

  console.log('Event type:', event.type);
  console.log('Event data:', event.data);

  res.status(200).send('OK');
});
Svix also provides verification libraries for Go, Ruby, PHP, Java, and C#. See the Svix documentation for all language examples.

Testing webhooks

Using Svix Play

  1. Go to Settings → Webhooks in your Appstle dashboard.
  2. Click on your endpoint.
  3. Use the Send Example feature to send a sample event.
  4. Verify your endpoint receives and processes the webhook correctly.

Local development

Use ngrok or localtunnel to expose your local server:
ngrok http 3000
Then add the generated public URL (e.g. https://abc123.ngrok.io/webhooks/appstle) as a webhook endpoint in your Appstle dashboard.

Retry schedule

If your endpoint does not return a 2xx status code, Svix retries automatically using exponential backoff over 5 attempts across 3 days. Endpoints that fail consistently are automatically disabled to prevent wasted resources. You can manually retry failed deliveries from the Svix dashboard, and view all delivery attempts in Settings → Webhooks → Message Logs.

Troubleshooting

ProblemSolution
Signature verification failsUse the exact signing secret from your dashboard. Do not modify the raw request body before verification.
TimeoutsReturn 200 OK immediately and process the event in a background job.
Wrong status codesReturn 2xx for all successful receipts, even if you have business logic errors.
CSRF protection blocking webhooksExempt your webhook endpoint from CSRF checks in your web framework.
Duplicate eventsWebhooks may be delivered more than once. Use the svix-id header to make your processing idempotent.
Log the raw webhook payload during development so you can inspect the exact data structure. Test with a simple endpoint that just logs and returns 200 OK before adding business logic.
Need help? Email support@appstle.com with your endpoint URL and the svix-id of any failing message.