Sending emails for failed payments

    Watch for account-related events using webhooks. Then keep your customers notified of problems with their subscription billing.

    Running a subscription business with recurring revenue is great. Your customer signs up for your service, enters their credit card details, and is subscribed to your plan. Stripe handles billing your customer at the interval defined in your plan, and life is good.

    But sometimes, when a customer’s subscription comes due, the payment fails. The customer might have canceled their card, the card might have expired, or the payment might be declined by the card issuer for some other reason. Suddenly, life is less good.

    Luckily, Stripe makes it easy to handle automated notifications in situations like these. We offer a built-in option to send your customers a standard email, on your behalf, for failed attempts to pay an invoice. For more about this simplified option, see Failed payment alerts.

    However, if you have specific design requirements for customizing emails’ trigger conditions, content, or other details, this recipe goes further. It explains how to set up a customized system that automatically notifies your customers when a subscription payment fails.

    Understanding webhooks and events

    This recipe uses webhooks to notify your customers about a failed payment. Webhook endpoints are essentially just scripts written in a server-side programming language, like PHP (used in this recipe) or Ruby. You could think of a webhook endpoint as a phone number that you give Stripe to call every time a thing happens in your Stripe account.

    Using webhooks in your Stripe account can save you a lot of time and effort, by providing an easy way to automate repetitive tasks. Webhooks are particularly useful for handling events that happen automatically behind the scenes, like recurring billing. Expanding on this recipe, you could use webhooks to notify customers about other problems with their account, to send custom receipts upon purchase, to update your application state to reflect some change, or to accomplish all sorts of other cool things.

    Stripe creates an Event object for each occurrence in your Stripe account that is worth notifying you about. Each event contains data explaining what happened. We create events for all sorts of things, from customers being created in your Stripe account to payouts being made to your bank account.

    Creating your webhook endpoint

    This recipe will create a webhook endpoint in PHP, although you can use your favorite programming language instead. The webhook script will listen for and respond to the invoice.payment_failed event. When it receives this event, the webhook script will send an email to the customer to let them know that their subscription payment had a problem.

    To start, you’ll need to create a new PHP file, called webhook.php, that begins with the following code:

    <?php // Sends an email to customers if their payment fails // If you're using Composer, use Composer's autoload require_once('vendor/autoload.php'); // Be sure to replace this with your actual test API key (switch to the live key later) \Stripe\Stripe::setApiKey("sk_test_4eC39HqLyjWDarjtT1zdp7dc"); // Retrieve the request's body and parse it as JSON $payload = @file_get_contents("php://input");

    We’ve placed a random API key in the code. Replace this with your actual API key to test this code for yourself.

    The @file_get_contents("php://input") line reads the data from the event that Stripe sends.

    You might notice that the example uses Composer. Composer is a great dependency management tool for PHP, but is not required. You could instead download Stripe’s PHP library, and use it directly in your application, but you’ll need to change the code above accordingly.

    Verifying the event

    Because the webhook endpoint is accessible to the internet, you should check the request’s signature to verify that it actually came from Stripe. You can do this with one of Stripe’s official libraries, by providing the event payload, the Stripe-Signature header, and the endpoint’s secret. If verification fails, your script should return an error. After the previous PHP code, add the following code to verify the signature:

    // You can find your endpoint's secret in your webhook settings $endpoint_secret = "whsec_..."; $sig_header = $_SERVER["HTTP_STRIPE_SIGNATURE"]; $event = null; try { $event = \Stripe\Webhook::constructEvent( $payload, $sig_header, $endpoint_secret ); } catch(\UnexpectedValueException $e) { // Invalid payload http_response_code(400); exit(); } catch(\Stripe\Exception\SignatureVerificationException $e) { // Invalid signature http_response_code(400); exit(); }

    Using the event data

    Next, your webhook script must examine and use the retrieved event data. To notify the customer that their payment failed, you’ll need their email address and the payment amount. If you create Customer objects in Stripe with a stored email address, you can obtain that email address right from the Customer object. Otherwise, if you don’t store email addresses on the Customer object, you could obtain the email address from your application’s database.

    To create variables for the email address and payment amount, add the following below your existing PHP code:

    if (isset($event) && $event->type == "invoice.payment_failed") { $customer = \Stripe\Customer::retrieve($event->data->object->customer); $email = $customer->email; // Sending your customers the amount in pennies is weird, so convert to dollars $amount = sprintf('$%0.2f', $event->data->object->amount_due / 100.0); }

    At this point, the webhook script has the data needed to contact the customer: their email address and the payment amount. If you wanted, you could also retrieve details about the invoice itself, such as the plan name.

    Sending notification emails

    If your server is configured to send email, you could use PHP’s mail() function to send email directly from your server. However, this recipe uses Mailgun’s transactional-emails API. To set up this section, you’ll want to sign up for a free Mailgun account.

    Once you’ve signed up, follow Mailgun’s instructions to install the Mailgun PHP client library. Mailgun also uses Composer in their example, but you can directly download and include the Mailgun PHP library if you prefer.

    To use Mailgun (after installing the library), you’ll need to add the code that actually sends your customer an email when their payment fails. First, configure Mailgun by adding the following to the script, immediately after the Stripe API key is set at the top of the page:

    // Require Mailgun stuff use Mailgun\Mailgun; // Be sure to replace with your Mailgun key and domain $mgClient = new Mailgun('key-yourKeyGoesHere'); $domain = "yourDomainGoesHere";

    Next, add the code to send the email. This should come after the variables you created from the event, but inside your conditional statement, so that emails are sent only for the correct event type:

    // Make the call to the client $result = $mgClient->sendMessage($domain, [ // Be sure to replace the from address with the actual email address you're sending from 'from' => 'YourApp Billing <>', 'to' => $email, 'subject' => 'Your most recent invoice payment failed', 'text' => 'Hi there, Unfortunately, your most recent invoice payment for ' . $amount . ' was declined. This could be due to a change in your card number, your card expiring, cancellation of your credit card, or the card issuer not recognizing the payment and therefore taking action to prevent it. Please update your payment information as soon as possible by logging in here:' ]);

    Publishing your webhook endpoint and adding it to Stripe

    Next, upload the webhook.php file to your server, then ensure that you’re able to access the URL for your webhook. (E.g., open in your browser. This should show a blank page, not an error message.)

    Once your webhook endpoint URL is available on the web, enable it in Stripe, so that you can test whether it’s working as expected. Make sure your Dashboard’s Viewing test data option is selected, then open your account’s Webhooks settings and click Add endpoint.

    In the resulting modal, add the URL for your new webhook, then click Add endpoint to close the modal. Your webhook endpoint is now enabled on your Stripe account (for test mode).


    To actually test the webhook, simulate an invoice payment failure (the event to catch when a subscription payment fails). You could test this by subscribing a customer to a plan with a very short trial period, and waiting for the invoice to fail.

    But for a faster webhook test, you can re-create this event by generating an invoice and immediately simulating a failed payment. To do that, you’ll create a Customer object and, from the multiple test card numbers listed in Stripe’s testing documentation, assign a test card that’s designed to fail for charges:

    1. Head back to your test Dashboard’s Customers page. At the top right, click + New to create a test customer.
    2. Enter your actual email address in the field. If prompted for authentication, enter your Stripe account’s password. Then click Create customer.
    3. Click the newly created customer’s entry. On the customer’s page, click Add Card, and enter the special test card number 4000 0000 0000 0341.
    4. On the same customer’s page, click Add Invoice Item, and enter $10.00 as the amount.
    5. Click Create invoice item.
    6. Once the invoice item has been added to the customer’s page, click Invoice Now next to the Pending Invoice Items header. Click Invoice now again to confirm.
    7. The invoice should appear—grayed out, since it hasn’t been paid yet—under the Invoices heading. Click the invoice to load the invoice page.
    8. Click the Pay Now button to attempt to pay the invoice. You’ll receive a message that “Your card was declined”, and the invoice.payment_failed event will be created. You can find this event by visiting your test events page and looking for the message “…invoice for $10.00 failed”.
    9. Click the event to view its details. Scroll to the bottom of the resulting page.

    If everything went as intended, you’ll see that the event was delivered to your webhook endpoint. Also, you’ll receive the email indicating that your payment failed. Huzzah!


    If your webhook shows “pending” in the Dashboard’s Webhooks settings, click the webhook URL to expand the details. If your server is sending back a non-2xx status, this indicates a problem with your code or server.

    If you are receiving an error, inspect your webhook script’s code. Be sure that it matches the code above, and that you’ve replaced placeholder API keys as needed. Reviewing your server’s error logs can also be useful in finding the problem’s source.

    Making the endpoint live

    Now it’s time to make this webhook live on your server, to send real payment failure notifications to your customers. First, update your code to change the test Stripe API key to your live key.

    Next, switch off your Dashboard’s Viewing test data option, then again visit your account’s Webhooks settings. Add the endpoint URL again, to register it in live mode.

    Your customers will now receive emails when their subscription payment fails, prompting them to update their card details. Remember to create a real update page to handle these customer requests!

    Other ideas for webhooks

    Although this webhook script was created to notify customers when their subscription payments fail, you could modify its code to do all sorts of interesting things. Some examples include:

    • Notifying your sales team when an invoice payment fails, so that they can follow up with the customer
    • Listening for invoice.payment_succeeded events, to send a custom invoice or receipt to your customer
    • Catching customer.subscription.deleted events, to send subscription cancellation notifications to your customer

    Was this page helpful?

    Feedback about this page?

    Thank you for helping improve Stripe's documentation. If you need help or have any questions, please consider contacting support.

    On this page