Sending Custom Email Receipts and Invoices

    Watch for successful charges, then automatically send custom email receipts or invoices to your customers.

    Although using Stripe to collect payments might feel seamless, it’s good to communicate to your customers when you charge them.

    Stripe offers a built-in option to send your customers a standard email receipt, on your behalf, for each successful payment. We also offer an option to send recurring or one-off invoices. For more about these standard options, see Email receipts and Sending invoices to customers.

    However, if you want to customize the emails’ trigger conditions, layout, line items, or other details, you need to write some code. This recipe shows you how to create a system for automatically sending custom email receipts or invoices.

    Understanding webhooks and events

    This recipe uses webhooks to send custom email receipts and invoices. A webhook endpoint is like a phone number that Stripe calls when certain things happen in your Stripe account.

    Stripe creates an Event object for each occurrence in your Stripe account that’s worth notifying you about, like the creation of a new customer in your Stripe account, or a payout to your bank account. Each event contains data explaining what happened.

    When you create a webhook, it listens for specific events, then takes action whenever those events occur. Technically, a webhook is just a script in a server-side language, like Ruby or PHP, that handles any events you specify in the Dashboard.

    For simplicity’s sake, this recipe focuses on email receipts. With a few slight changes, you can modify it to send automatic email invoices.

    Setting up the framework

    This recipe creates a webhook endpoint in Ruby, using the Sinatra framework. Feel free to use your preferred programming language instead. To start, create a directory for your webhook, and move into that directory:

    mkdir webhook-handler && cd webhook-handler

    Next, create the files for your webhook handler:

    touch email_receipts_webhook.rb config.ru Gemfile email_receipts.erb

    Next, add the following lines to your config file:

    require './webhook-handler'
    run Sinatra::Application

    Then add the Ruby gems you need to the Gemfile:

     source 'https://rubygems.org'
     ruby "2.2.3"
    
     gem 'sinatra'
     gem 'stripe'
     gem 'postmark'

    Finally, run Bundler to install the required gems:

    bundle install

    Creating your webhook endpoint

    The email_receipts_webhook.rb file does all the work, listening for POST requests from Stripe and taking actions depending on the event type. Open the file in your text editor, and create an outline for your webhook script:

    require 'sinatra'
    require 'stripe'
    
    # Read variables from your environment
    Stripe.api_key = ENV['STRIPE_KEY']
    
    # You can find your endpoint's secret in your webhook settings
    endpoint_secret = ENV['STRIPE_ENDPOINT_SECRET']
    
    # Using Sinatra, listen for a POST to the /webhook endpoint
    post '/webhook' do
      # Retrieve the payload from the webhook 
      payload = request.body.read

    Whenever Stripe calls your webhook, this code requests the event payload. This example uses Stripe’s Ruby library.

    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 (you’ll add the actual secret later). After the previous Ruby code, add the following lines to verify the signature:

       # Verify signature to make sure the event came from Stripe
       signature = request.env['HTTP_STRIPE_SIGNATURE']
       event = nil
    
       begin
         event = Stripe::Webhook.construct_event(payload, signature, endpoint_secret)
         status 200
       rescue JSON::ParserError => e
         # Invalid payload
         # If this happens, log the problem to investigate
         status 400
       rescue Stripe::SignatureVerificationError => e
         # Invalid signature
         # If this happens, log the problem to investigate
         status 400
       end
     end

    If verification fails, Stripe returns an error.

    Using the event data

    Next, your webhook script must examine and use the retrieved event data.

    You only want to send an email if a charge succeeds. The other requirements for an email receipt are the customer’s email address and the payment amount. If you create Customer objects in Stripe, you can obtain the email address right from the object. Otherwise, you can obtain the email address that you previously stored in your application’s database.

    Add the following code block above the status 200 line, so that the webhook only works if the event type is right and it contains the information you need. Include here any other details you’d like to report in the receipt (e.g., a line item for tax):

    # Only respond to `charge.succeeded` events, and only if
    # there's an associated email address
    if event.type == 'charge.succeeded' && event.data.object.receipt_email && event.data.object.amount
    end

    At this point, the webhook script has the data needed to send the customer a receipt: their email address and the payment amount in the charge.succeeded event.

    Sending notification emails

    To send emails from your server, feel free to use whichever email service is most familiar to you. This recipe uses Postmark’s transactional-emails API. To complete this section, first sign up for a free Postmark account.

    Once you’ve signed up, add a new domain or email address. In its basic test mode, Postmark requires you to send and receive from the same email address.

    Next, add the code to actually send your customer an email when their payment succeeds. Require the Postmark gem at the top of the email_receipts_webhook.rb file:

    require 'postmark'

    Immediately after the line that sets your Stripe API key, instantiate the Postmark client:

    # Create an instance of Postmark::ApiClient:
    client = Postmark::ApiClient.new ENV['POSTMARK_KEY']

    Next, add the code to send the email. This should be inside your conditional statement, so that emails are sent only for the correct event type:

    # Send an email
    result = client.deliver(
      # In Postmark’s test mode, the recipient’s address should be the same as the mailer’s address
      from: event.data.object.receipt_email,
      to: event.data.object.receipt_email,
      subject: 'Your SITE-NAME.COM email receipt',
      html_body: html_email(event),
      text_body: text_email(event)
    )

    Now it’s time to add the email content. First, sending your customers the amount in pennies is weird, so convert to dollars:

    def format_amount(amount)
      # For amounts in the thousands, this also formats with approriate commas
      sprintf('$%0.2f', amount.to_f / 100.0).gsub(/(\d)(?=(\d{3})+(?!\d))/, "\\1,")
    end

    For mail clients that don’t support HTML, include a text version of the receipt. Define a text_email method like the one below, which pulls order details from your Stripe account:

    # Text email
    def text_email(event)
      <<-EOF
      Hi there,
    
    
      Thank you for your purchase: here's your payment receipt!
    
    
      Questions? Please contact support@SITE-NAME.COM.
    
    
      =================================================================
      SITE-NAME RECEIPT - Payment for #{event.data.object.description}
    
    
      Total: #{format_amount(event.data.object.amount)} #{event.data.object.currency}
    
    
      Charged to: #{event.data.object.source.brand} ending in #{event.data.object.source.last4}
      Payment ID: #{event.data.object.id}
      Date: #{Time.now.strftime("%m/%d/%Y")}
    
    
      SITE-NAME.COM
      510 Townsend Street
      San Francisco, CA 94110
      =================================================================
    
    
      EOF
    end

    Make sure you edit the template with your own business details.

    For mail clients that handle HTML, use your email_receipts.erb file. Feel free to write your own HTML, or build on our basic template.

    When you’re satisfied with your HTML version, include it in email_receipts_webhook.rb, below your existing code:

    class RenderBindings
      attr_reader :event
    
      def initialize(event)
        @event = event
      end
    
      def get_binding
        binding
      end
    end
    
    def html_email(event)
      ERB.new(File.read("views/email_receipts.erb")).result(RenderBindings.new(event).get_binding)
    end

    The first part adds a new class that lets email_receipts.erb populate the HTML receipt with the correct event data from Stripe, just like the text version.

    Your webhook handler now knows to send a notification email whenever the endpoint is called.

    Before implementing your webhook, make sure you’re not about to send duplicate receipts to your customers. Go to your email settings, and uncheck the box for Successful payments.

    Adding your webhook to Stripe

    You’re ready to add the webhook endpoint to your Stripe account. Upload the email_receipts_webhook.rb file to your server, and test that it’s accessible from a public URL using a service like ngrok. It’s OK if the request 404s—you still need to add a webhook secret to your code.

    Next, enable the webhook in the Stripe Dashboard to test whether it’s working as expected. With your Dashboard’s Viewing test data option selected, open your account’s Webhooks settings, and click Add endpoint.

    Add the URL for your new webhook and click Add endpoint. Your webhook endpoint is now enabled on your Stripe account (for test mode).

    Before you can test your webhook, you need its endpoint secret. Click on your new webhook, and from the Signing secret section, select Click to reveal. Use this endpoint secret in the command line when you set your environment variables.

    Testing

    With your webhook ready to go, make sure everything is working. You can start the server locally with the following command:

    STRIPE_KEY='sk_test...REST-OF-YOUR-KEY' POSTMARK_KEY='YOUR-POSTMARK-KEY' STRIPE_ENDPOINT_SECRET='whsec_...REST-OF-YOUR-SECRET' ruby email_receipts_webhook.rb

    Because your webhook now has all it needs, you should see Sinatra start up, and the endpoint in ngrok should return a 2xx status when you hit it.

    To actually test the webhook, simulate a successful charge—that’s the event you’re now set up to catch.

    To test a recurring payment, you can subscribe a customer to a plan with a very short trial period and wait for the payment to succeed. For a faster webhook test, generate an invoice and immediately simulate a successful payment.

    First, create a Customer object in the Dashboard:

    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, then click Create customer.

    Next, assign the customer a test card number that’s designed to succeed for charges, and try charging it:

    1. 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 0077.
    2. On the same customer’s page, click Add invoice item, enter $10.00 as the amount, and click Create invoice item.
    3. 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.
    4. 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.
    5. Click the Charge customer button to attempt to pay the invoice. To confirm that the charge.succeeded event was created, look for the message “…was charged $10.00 USD.”
    6. Click the event to view its details. Scroll to the bottom of the resulting page.

    If all the steps went as intended, the event was successfully delivered to your webhook endpoint. To confirm, look for an email with an itemized receipt at the address you provided. Note that Stripe’s testing documentation lists multiple other card numbers for trying different outcomes.

    Troubleshooting

    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 receive an error, inspect your webhook code. Be sure that it matches the code above, and that you’ve replaced placeholder API keys. Reviewing your server’s error logs can also help you the problem’s source.

    Making the endpoint live

    Now it’s time to make this webhook live on your server and begin sending real receipts to your customers. First, update your code to change the test Stripe API key to your live key. In the Postmark email code, set your from: field to your actual company domain. Depending on your email volume, this may require you to upgrade your Postmark account.

    Next, switch off your Dashboard’s Viewing test data option, then revisit your Webhooks settings. Add the endpoint URL again to register it in live mode. Reveal the new endpoint secret to replace your test one.

    Your customers can now get emails when you receive their payment.

    Other ideas for webhooks

    Although this webhook script was created to send automatic receipts to customers, you could modify the 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
    • Sending automatic invoices whenever a charge is created
    • Catching customer.subscription.deleted events to send subscription cancellation notifications to your customer

    Questions?

    We're always happy to help with code or other questions you might have! Search our documentation, contact support, or connect with our sales team. You can also chat live with other developers in #stripe on freenode

    Was this page helpful? Yes No

    Send

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