Webhooks

Receive real-time events

CroissantPay sends webhooks to your server when subscription events occur. Use webhooks to update your database, send emails, or trigger other actions in real-time.

Setting Up Webhooks

  1. Configure your endpoint - Go to your app settings in the CroissantPay dashboard and enter your webhook URL.
  2. Save your secret - CroissantPay will generate a webhook secret. Store this securely - you'll need it to verify signatures.
  3. Handle events - Implement an endpoint that receives POST requests and processes events.

Event Types

CroissantPay uses RevenueCat-compatible event types, making migration easy. Events are triggered when Apple App Store or Google Play send notifications.

Initial Purchase Events
INITIAL_PURCHASENew subscription was purchased for the first time
NON_RENEWING_PURCHASEA non-renewing or one-time purchase was made
Renewal Events
RENEWALSubscription was automatically renewed
PRODUCT_CHANGEUser upgraded or downgraded their subscription
Cancellation Events
CANCELLATIONUser turned off auto-renewal (will expire at period end)
UNCANCELLATIONUser re-enabled auto-renewal before expiration
Billing Events
BILLING_ISSUEPayment failed, subscription entering grace period or retry
BILLING_ISSUE_RESOLVEDPayment issue was resolved, subscription is active again
Expiration & Refund Events
EXPIRATIONSubscription has expired and is no longer active
REFUNDPurchase was refunded or revoked by the store
Trial Events
TRIAL_STARTEDFree trial period started
TRIAL_CONVERTEDTrial converted to paid subscription
TRIAL_CANCELLEDUser cancelled during trial (before conversion)
Grace Period Events
GRACE_PERIOD_ENTEREDSubscription entered grace period due to billing issue
GRACE_PERIOD_EXITEDGrace period ended (either resolved or expired)
Pause Events (Android only)
SUBSCRIPTION_PAUSEDUser paused their subscription (Google Play only)
SUBSCRIPTION_RESUMEDUser resumed their paused subscription
Test Events
TESTTest webhook sent from the dashboard

Webhook Payload

Every webhook event includes comprehensive subscriber, subscription, and entitlement data. This format is designed to be compatible with RevenueCat webhooks:

{
  "api_version": "1.0",
  "event": {
    "id": "evt_abc123...",
    "type": "RENEWAL",
    "app_id": "app_xyz789...",
    "event_timestamp_ms": 1705315800000,
    
    // Complete subscriber information
    "subscriber_info": {
      "id": "sub_123...",
      "app_user_id": "user_456",
      "original_app_user_id": null,
      "aliases": [],
      "first_seen_at": "2024-01-01T00:00:00Z",
      "last_seen_at": "2024-01-15T10:30:00Z",
      "attributes": {}
    },
    
    // Product details
    "product": {
      "id": "prod_abc...",
      "identifier": "pro_monthly",
      "store_product_id": "com.yourapp.pro.monthly",
      "platform": "ios",
      "type": "auto_renewable_subscription",
      "display_name": "Pro Monthly",
      "subscription_period": "P1M",
      "trial_period": "P7D"
    },
    
    // Subscription state
    "subscription": {
      "id": "subscription_123...",
      "status": "active",
      "original_transaction_id": "1000000123456789",
      "latest_transaction_id": "1000000987654321",
      "purchase_date": "2024-01-15T10:30:00Z",
      "original_purchase_date": "2023-12-15T10:30:00Z",
      "expires_date": "2024-02-15T10:30:00Z",
      "auto_renew_enabled": true,
      "is_trial_period": false,
      "is_intro_period": false,
      "canceled_at": null,
      "cancellation_reason": null,
      "grace_period_expires_date": null
    },
    
    // Current entitlements snapshot
    "entitlements": [
      {
        "id": "ent_abc...",
        "identifier": "premium",
        "is_active": true,
        "expires_date": "2024-02-15T10:30:00Z",
        "product_identifier": "pro_monthly"
      }
    ],
    
    "platform": "ios",
    "environment": "production",
    
    // Original store notification info
    "store_event": {
      "type": "DID_RENEW",
      "event_id": "apple_notification_uuid"
    }
  }
}

Verifying Signatures

Always verify webhook signatures to ensure events are from CroissantPay. We include a signature in the X-CroissantPay-Signature header.

Using the SDK (Node.js)

import { verifyWebhookSignature, constructEvent } from '@croissantpay/react-native';

app.post('/webhooks/croissantpay', express.raw({ type: 'application/json' }), (req, res) => {
  const signature = req.headers['x-croissantpay-signature'];
  const payload = req.body.toString();

  try {
    const event = constructEvent(
      payload,
      signature,
      process.env.CROISSANTPAY_WEBHOOK_SECRET
    );

    // Handle the event
    switch (event.type) {
      case 'subscription.renewed':
        await handleRenewal(event.data);
        break;
      case 'subscription.expired':
        await handleExpiration(event.data);
        break;
      // ... handle other events
    }

    res.json({ received: true });
  } catch (err) {
    console.error('Webhook signature verification failed:', err.message);
    return res.status(400).send('Webhook Error');
  }
});

Manual Verification

import crypto from 'crypto';

function verifySignature(payload, signature, secret) {
  const expectedSignature = crypto
    .createHmac('sha256', secret)
    .update(payload)
    .digest('hex');

  return crypto.timingSafeEqual(
    Buffer.from(signature.replace('sha256=', '')),
    Buffer.from(expectedSignature)
  );
}

Best Practices

Return 200 quickly

Return a 200 response as soon as possible. Process the webhook asynchronously if needed.

Handle duplicates

Use the event ID to detect and handle duplicate deliveries. Store processed event IDs.

Implement idempotency

Make sure processing the same event twice doesn't cause issues.

Use HTTPS

Always use HTTPS endpoints in production.

Monitor failures

Set up alerting for webhook failures. CroissantPay retries failed webhooks up to 3 times.

Retry Policy

If your endpoint doesn't return a 2xx response, we'll retry:

  • 1st retry: After 2 seconds
  • 2nd retry: After 4 seconds
  • 3rd retry: After 8 seconds

After 3 failed attempts, the webhook will be marked as failed. You can view failed webhooks in the dashboard.

Testing Webhooks

Use the "Send Test" button in your app settings to send a test webhook to your endpoint. This helps verify your integration before going live.

For local development, you can use tools like ngrok to expose your local server to the internet.