---
title: 'Peeking under the hood of Stripe Invoicing'
summary: 'Stripe Invoicing offers a no-code solution for sending invoices to customers. Because this option handles the complexity of all underlying API calls, developers sometimes struggle to understand the different phases a Stripe invoice goes through, which is problematic when attempting to debug payment failures. '
authors:
  - 'David Edoh-Bedi'
date: '2024-08-26'
topics:
  - 'Workbench'
  - 'Billing'
type: 'Blog'
---

[Stripe Invoicing](https://docs.stripe.com/no-code/invoices) offers a no-code solution for sending invoices to customers. Because this option handles the complexity of all underlying API calls, developers sometimes struggle to understand the different phases a Stripe invoice goes through, which is problematic when attempting to debug payment failures. 

This post uses the new [Stripe Workbench](https://docs.stripe.com/workbench) debugging tool to analyze what happens behind the scenes as an Invoice goes through its lifecycle. 

### What is Workbench?

[Workbench](https://docs.stripe.com/workbench) is a context-aware tool that allows you to build and debug your Stripe integrations from anywhere in the Dashboard. In response to feedback from Stripe developers, this new feature centralizes previously disparate developer tooling into one central and constantly accessible location. Among other things, you can use Workbench to inspect API objects and run requests on them using the [built-in Shell](https://docs.stripe.com/workbench/shell).

To get started, navigate to the [Workbench](https://dashboard.stripe.com/workbench) page in your dashboard and turn the feature on.

### Invoice creation in the dashboard

Navigate to the **Invoices** tab in the dashboard and click on the **Create test invoice** option in the upper right corner. You’re then presented with the Invoice creation wizard, where you can populate all the relevant fields for your invoice. Specifically, make sure to add values for **Customer** and **Items** (from your [product catalog](https://docs.stripe.com/products-prices/getting-started#create-products-prices)). 

Make sure to have Workbench viewable so you can observe the underlying API calls. 

![](/images/invoice-peek/peek-1.png)

![](/images/invoice-peek/peek-2.png)

Exit the wizard by clicking the **X** in the upper left corner. At this point, your invoice is saved in a draft state.

Now take a look at the data map in the **Inspector** tab of Workbench. This shows you the JSON representation of all the objects created so far, and their hierarchy. Notably:

* The Invoice is the top level object. It has a two associated objects: 
 * Customer - the individual making the transaction 
 * Invoice item - the item being purchased

 The Invoice object itself comprises numerous fields, like `status` which tells you what phase it is currently in. This status is initially set to `draft`. 

* A Price object with an associated Product. This is a standalone object and not a direct child of the Invoice, which makes sense as products have a one to many relationship with invoices. 

![](/images/invoice-peek/peek-3.png)

Next, switch to the **Logs** tab. This tab shows the recent API calls that have been made in your integration. In this example, there are two calls: one for Invoice creation and another for the Invoice Item creation. 

![](/images/invoice-peek/peek-4.png)

Lastly, look at the **Events** tab to see which ones get fired as part of the invoice creation process. 

![](/images/invoice-peek/peek-5.png)

The [invoice.created](https://docs.stripe.com/api/invoices/create) and [invoiceitem.created](https://docs.stripe.com/api/invoiceitems/create) events align with what you see in the **Logs** tabs, namely the fact that an Invoice was created and an Invoice Item added to it. 

There’s also an `invoice.updated` event that’s worth analyzing further. One way to do that is to look at the `previous_attributes` section at the bottom of the Event JSON. This shows you what attributes changed from the previous variation of the Invoice object, leading to the `invoice.updated` event being fired. The lines attribute seems particularly interesting for a further look. 

![](/images/invoice-peek/peek-6.png)

In the same JSON for the `invoice.updated` event, look at the updated lines attribute. It shows that the Invoice Item was added as expected, but there’s also a new [Invoice Line Item](https://docs.stripe.com/api/invoices/line_item) object. This is a nested resource automatically generated by Stripe to represent the individual line items in the invoice. It cannot be created directly through the API. 

![](/images/invoice-peek/peek-7.png)

To recap, for Invoices created in the dashboard:

1. An underlying Invoice object is created, via a call to the `v1/invoices` endpoint. It starts in a `draft` status. This process fires an `invoice.created` event.  
2. An Invoice Item is created separately, via the `v1/invoiceitems` endpoint, and appended to the Invoice. This process fires an `invoiceitem.created` event, followed by an `invoice.updated` event.  
3. There’s an underlying Invoice Line Item object automatically generated by the API

With the invoice is created, what happens next?

### Sending the invoice to your customer

Navigate back to the previously created invoice in your dashboard, and click the **Send invoice** button to simulate sending it to your customer.

![](/images/invoice-peek/peek-8.png)

![](/images/invoice-peek/peek-9.png)

Check back to the data map in the **Inspector** tab of Workbench. There’s a new object in the Invoice hierarchy: a [Payment Intent](https://docs.stripe.com/api/payment_intents). This object is at the core of Stripe’s payment API. It’s a [state machine](https://en.wikipedia.org/wiki/Finite-state_machine) which transitions through different phases over the course of the payment process.

![](/images/invoice-peek/peek-10.png)

Switching over to the **Events** tab, you see the events that are fired as part of sending the invoice. 

![](/images/invoice-peek/peek-10a.png)

One way to analyze `invoice.updated` events is to look at the `previous_attributes` section. In this case, the `status` is included here, which means its value is updated as part of this event.

![](/images/invoice-peek/peek-11.png)

The value of the `status` attribute in the Event JSON has changed to `open`.

![](/images/invoice-peek/peek-12.png)

Another noteworthy event is `invoice.finalized`. The event description indicates that finalizing is an operation on draft invoices. This changes the invoice’s status to `open`, meaning it’s ready for payment.

![](/images/invoice-peek/peek-13.png)

When an invoice is sent to the customer:

* A Payment Intent object is created, indicating that you’re ready to collect a payment. 
* The Invoice is finalized and its status changes from `draft` to `open`.

Now you can simulate collecting payment from the customer.

### Charging the customer

Navigate once again to the previously created invoice in your dashboard, and click the **Charge customer** button.

![](/images/invoice-peek/peek-14.png)

In the resulting pop-up window, you see a message about the invoice not being editable after payment attempts. This is important to note, as it signifies that whatever state the invoice ends up in after this process is terminal. 

![](/images/invoice-peek/peek-15.png)

Proceed with charging the customer and switch back to the Inspector. There is another new object in the data map: a [Charge](https://docs.stripe.com/api/charges). This represents an atomic charge operation and is created as part of the Payment Intent flow.

![](/images/invoice-peek/peek-16.png)

Switching to the **Logs** tab, there was a call to the Pay endpoint, as expected.

![](/images/invoice-peek/peek-17.png)

The **Events** tab shows various activities. The `payment_intent.` and `charge.` events confirm what you see in the **Logs** and **Inspector** tabs. Take a closer look at the `invoice.updated` event specifically.

![](/images/invoice-peek/peek-18.png)

Once again, the `status` shows up in the `previous_attributes` hash, which means it changed as part of this event.
![](/images/invoice-peek/peek-19.png)

The updated status is `paid`, which aligns with the `invoice.payment_succeeded` event that is fired.

![](/images/invoice-peek/peek-20.png)

As part of the invoice payment process:

* The Payment Intent is captured - which creates a charge 
* The invoice transitions from `open” to “paid”

### Canceling an invoice

There are two cases to consider here: an invoice has not been sent to the customer, or an invoice has already been sent.

1. Invoice has not been sent to the customer

To simulate this, create a new invoice using the wizard. Exit out of the wizard to keep the invoice in draft state. Make sure you can see the invoice in the Inspector.

In the invoices page of the dashboard, click on the **ellipsis** and select the **delete draft invoice** option in the dropdown.

![](/images/invoice-peek/peek-22.png)

You are presented with a message about this action being irreversible, so this is a terminal state for the invoice, meaning that it cannot be further modified. 

![](/images/invoice-peek/peek-23.png)

You can confirm with Workbench that the invoice record is gone from Stripe and is no longer accessible.

![](/images/invoice-peek/peek-24.png)

2. Invoice has been sent to the customer

To simulate this, create an invoice using the wizard and send it to the customer, as previously shown. Then, find the invoice in the dashboard and choose the **Change invoice status** under the ellipsis in the right hand corner. 

![](/images/invoice-peek/peek-25.png)

Here, additional statuses can potentially apply to invoices. Choose the **Void** status in the list and click on **Update status**. 

![](/images/invoice-peek/peek-26.png)

The **Logs** tab confirms a call to the `/invoices/void` endpoint.

![](/images/invoice-peek/peek-27.png)

There are a few events triggered as part of this process:

* The previously created Payment Intent gets canceled (`payment_intent.canceled` event), given that you no longer want to collect a payment on this invoice. 
* There’s an `invoice.voided` event corresponding to the `/invoices/void` call.

![](/images/invoice-peek/peek-28.png)

In the `invoice.updated` event details, the status once again changes. It was previously set to `open`: 

![](/images/invoice-peek/peek-29.png)

After the void operation, the invoice status changes to `void`:

![](/images/invoice-peek/peek-30.png)

To recap, there are two ways of canceling invoices:

1. By deleting an invoice that’s in `draft` status. This deletes the invoice record from Stripe. 
2. By voiding an invoice that’s in `open` status. This option preserves the invoice record, so you can use it for bookkeeping as needed. 

### Wrapping up

Using the various tools in Workbench, you can analyze any object in the Stripe dashboard and map out its hierarchy and observe its different state changes through the payment lifecycle. The approach you followed here with Invoices can be replicated to other parts of the Stripe API.
