Sending Emails for Failed Payments

Watch for account-related events using webhooks. Then keep your customers notified of problems with their subscription billing. If you need help after reading this, check out our answers to common questions or chat live with other developers in #stripe on freenode.

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 may have expired, or the payment might be declined by the bank for some other reason. Suddenly, life is less good. Thankfully, Stripe makes handling automated notifications in situations just like these pretty easy. This recipe explains how to set up a system that automatically notifies your customers when a subscription payment fails.

Understanding webhooks and events

Webhook endpoints are essentially just scripts written in a server-side programming language like PHP 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. Webhooks are particularly useful for handling events that happen automatically behind the scenes, like recurring billing.

Using webhooks in your Stripe account can save you a lot of time and effort, and provides an easy way to automate repetitive tasks. The only thing webhooks are being used for in this recipe is to notify your customers about a failed payment, but you could use webhooks to notify customers about problems with their account, send custom receipts upon purchase, update your application state to reflect some change, or all sorts of other cool things.

When something worth notifying you about occurs in your Stripe account, we create an event. Events are essentially just objects that contain data explaining what happened. We create events for all sorts of things, from customers being created in your Stripe account to transfers being made to your bank account.

Creating your webhook endpoint

This recipe will create a webhook endpoint in PHP, but 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 sends an email to the customer to let them know 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_BQokikJOvBiI2HlWgH4olfQ2");

// Retrieve the request's body and parse it as JSON
$input = @file_get_contents("php://input");
$event_json = json_decode($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. The json_decode() function converts that data to a PHP variable for later use.

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

Verifying the event

Since the webhook endpoint is accessible to the scary Internet, it should ensure it received a valid event from Stripe before taking action. After the previous PHP code, add a new line to retrieve the event data from Stripe, using the received event ID:

// Check against Stripe to confirm that the ID is valid
$event = \Stripe\Event::retrieve($event_json->id);

Using the event data

Next, the retrieved event data must be examined and used. To notify the customer that their payment failed, their email address and the payment amount are required. If you create Customer objects in Stripe, with a stored email address, you can obtain that email address right from the Customer object; you could alternatively obtain the email address from your application’s database if you don’t store email addresses on the Customer object.

The PHP code will create some variables for the email address and payment amount. Add the following below your existing 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 payment amount. If you wanted, you could also retrieve details about the invoice itself, such as the plan name.

Sending notification emails

Although it’s possible to send email directly from your server, this recipe uses Mailgun. You’ll want to sign up for a free Mailgun account to get started. (Alternatively, you can use PHP’s mail() function directly, if your server is configured to send email.)

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

To use Mailgun (after installing the library), you’ll need to add the code to actually send an email to your customer 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 actually send an email–this should come after the variables you created from the event, but inside of your conditional statement (so emails are only sent for the correct event type).

// Make the call to the client
$result = $mgClient->sendMessage($domain, array(
  // Be sure to replace the from address with the actual email address you're sending from
  'from'    => 'YourApp Billing <mailgun@sandbox695f17069ce947b6aa93805a0a6111f3.mailgun.org>',
  '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 or your card expiring, cancelation of your credit card,
  or the bank not recognizing the payment and taking action to prevent it.

  Please update your payment information as soon as possible by logging in here:
  https://yoursite.com/login'
));

Note: This example includes a link to your website where the customer can update their card details; you’ll want to be sure you have a form on your site set up to handle this workflow. You can find a tutorial that covers handling customer card updates on your website here.

Upload the webhook.php file to your server and ensure you’re able to access the URL for your webhook (e.g., by going to http://yoursite.com/webhook.php in your browser, which should show a blank page, not an error message).

Adding your webhook endpoint to Stripe

Once your webhook endpoint URL is live on the web, test it to make sure it’s working as expected. Visit the webhooks tab in your account settings, then click Add endpoint.

In the modal window that appears, add the URL for your new webhook and set the mode to Test. Click Create endpoint.

Your webhook endpoint is now enabled on your Stripe account (for test mode).

Testing

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 wait for the invoice to fail. But to run a webhook test a bit faster, you can recreate the same event by generating an invoice and then failing in the attempt to pay it. To do that, use a test card that can be added to a Customer object but fails for charges: card number 4000 0000 0000 0341, listed in Stripe’s testing documentation.

  1. Head back to the Customers page on your test Dashboard and click + New on the top-right area of the page to create a test customer.
  2. Enter your actual email address in the field and click Create customer.
  3. Click Add Card on the newly created customer’s page in the Dashboard, and enter the special test card number ending in 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, click Invoice Now next to the Pending Invoice Items header on the customer page. 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 payment”.
  9. Click the event to view the details and scroll to the bottom of that page. You’ll see that the event was delivered to your webhook endpoint.

If everything went well, you should receive the email indicating that your payment failed. Huzzah!

Troubleshooting

If your webhook shows “pending” in the Dashboard, click the webhook URL to expand the details and be sure your server isn’t sending back a non-2xx status, which would indicate a problem with your code or server. If you are receiving an error, take a look at the code in your webhook script to be sure things match the code above (and that you’ve replaced API keys as needed). Reviewing your server’s error logs can also be useful in finding the source of the problem.

Making the endpoint live

As the last step, 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, visit the webhooks page in your account settings again, and add the endpoint URL 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 page for doing so.)

Other ideas for webhooks

Though this webhook script was created for the purpose of notifying customers when their subscription payments fail, you could also modify this code to do all sorts of interesting things. Some examples include:

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