The two systems aren't connected by default
Management + Billing manages access (purchases, renewals, expirations, cancellations). ESP manages mailing lists (mailing-list membership, send activity, suppression flags). Changes in one do not propagate to the other automatically. To synchronize them you'll build a webhook listener that translates Management + Billing lifecycle events into ESP subscribe/unsubscribe API calls.
The same applies in reverse: a user toggling their newsletter preferences in ESP does not change their Management + Billing access. If you need that link, build it explicitly.
The webhook events that drive access or subscription changes
Subscribe to the following Piano webhooks and route them to a single endpoint that you control. The endpoint just needs to respond with HTTP 200; no payload format is required for the acknowledgement.
Adding users to subscribers-only lists
-
access_granted.new_purchase— fires when a user buys a new subscription. Use this to add the user to the subscribers-only mailing list (and optionally remove them from the free list). -
subscription_created.new_purchase— the equivalent subscription-level event for new purchases. Either event can drive the membership update.
Removing users when access ends
-
access_revoked.subscription_canceled— fires when a subscription is canceled, either by the user or because a renewal failed. Use this to move the user off the subscribers-only list. -
subscription_ended.subscription_canceled— the subscription-level counterpart.
Special case: scheduled-term expirations
-
subscription_ended.subscription_expired— fires when a scheduled, fixed-length term (for example, a promotional trial or a one-off gift term) reaches its end date. This is the event you need for term expirations. Theaccess_revokedwebhook does not fire for scheduled-term expirations, so if you rely only onaccess_revokedyou will leave expired users on the subscribers-only list. -
Route
subscription_ended.subscription_expiredto the same handler endpoint you use for cancellations so the membership-update logic is consistent.
User-level events
-
user_created.user_created— fires when a new Identity Management account is created. Useful for auto-opt-in to a general newsletter list. -
user_updated.user_updated— fires when a user's profile changes. Since the event does not include previous and new values, store anold_emailvalue of your own (for example, in a custom user field) so you can detect email changes and update mailing-list membership accordingly.
A working pattern
A typical implementation looks like this:
-
Stand up one webhook endpoint that receives all the events listed above. Respond
200quickly; do the work asynchronously. -
On
access_granted.new_purchaseorsubscription_created.new_purchase, look up the user's email, call the ESP subscribe API to add them to the subscribers list, and call the ESP unsubscribe API to remove them from the free list if applicable. -
On
access_revoked.subscription_canceledorsubscription_ended.subscription_canceled, do the reverse: remove from subscribers-only, optionally add back to the free list. Avoid resubscribing users who have manually opted out — check the user's prior opt-out state before re-adding them. -
On
subscription_ended.subscription_expired, run the same removal logic as for cancellations. This is the only path that catches scheduled-term expirations. -
On
user_updated.user_updated, detect email changes by comparing against your storedold_email. If the email changed, unsubscribe the old address from all relevant lists and subscribe the new address.
Segmenting by subscription type
If you operate multiple subscription tiers (for example, monthly, quarterly, annual), create a separate ESP mailing list per tier — "Monthly Active," "Quarterly Active," "Annual Active" — and inspect the webhook payload for the term details when adding the user. Driving a triggered ESP campaign off list membership then sends the right newsletter to each cohort automatically.
If you need to delay the addition to a list (for example, add a user 10 days after subscribing), schedule that addition externally — for example, with a background job that runs on a cron schedule and calls the ESP subscribe API at the appropriate time. The same applies to scheduled removals.
Win-back and lapsed-subscriber campaigns
For win-back campaigns targeting churned subscribers, query the API for users with the relevant subscription status — for example, terms that have ended or been canceled — and use that list to build a dedicated ESP segment. Combine it with a promotional term or promo code (free trials of recurring terms are a common pattern; existing terms with active subscribers cannot have their prices changed, so new terms are usually required). The same list can drive a targeted Composer experience that surfaces the offer on-site.
Reconciliation: a safety net against drift
Webhook delivery and your downstream processing can fail in ways that leave ESP and Management + Billing out of sync. To catch drift, run a nightly reconciliation job:
-
Pull the current set of users with access via the Access Check API (
expire_dateindicates whether the user still has access) or the audit API. -
For each user, query ESP for their current mailing-list memberships.
-
Add or remove users to bring the two views back into alignment.
Reconciliation also handles legacy users (people who had access before the webhook listener was in place), manual unsubscribes and spam reports (which deactivate the user in ESP without firing a Management + Billing event), and re-subscription edge cases where a re-subscribing user wasn't re-added to lists automatically.
Troubleshooting list-vs-access discrepancies
If the count of active subscribers on a mailing list doesn't match the count of users with access in Management + Billing, the usual causes are:
-
Manual unsubscribes and spam complaints. Users who unsubscribe through the email footer or mark messages as spam are flagged inactive in ESP but still appear on the list. This is expected behavior.
-
Re-subscription not handled. Users who unsubscribed and later re-subscribed may not have been added back through your webhook handler if it didn't account for re-subscribes.
-
Legacy users. Users who gained access before the integration existed weren't captured by the webhooks.
-
Inactive accounts. Users marked inactive in Management + Billing (cancelled, expired, or paused) may still appear in older mailing-list exports.
Audit the lists periodically against the Management + Billing access state to bring them into alignment, and confirm your webhook handler covers all the cases above — especially the often-missed subscription_ended.subscription_expired event for scheduled-term expirations.
Webhook endpoint requirements
-
Use HTTPS.
-
Respond with HTTP
200(or any 2xx). -
No specific response body is required.
-
Use the same endpoint for all the events in this article; differentiate based on the payload's
eventandaction_typefields.
Related APIs
|
Purpose |
API |
|---|---|
|
Check whether a user has access to a resource |
Access Check API |
|
Retrieve subscription details |
|
|
Retrieve user details (including email) |
|
|
Subscribe a user to an ESP mailing list |
|
|
Unsubscribe a user from an ESP mailing list |
|
|
Check whether a user is on a specific list |
|