# Using a Stripe Webhook to transfer tax to the platform account

{% hint style="info" %}
Please note this feature requires the use of backend workflows. You will need to be on a paid Bubble plan to use backend workflows.
{% endhint %}

As mentioned in the [previous section](tax-for-marketplaces) of the plugin documentation, if you're using Stripe Tax when creating a **Destination** charge you will need to transfer the tax component of the payment back to the platform accoun&#x74;_._&#x20;

There are a number of ways to do this, but the most robust ([which is recommended by Stripe](https://docs.stripe.com/tax/tax-for-marketplaces?charge-type=destination-charges#tw-checkout-pl)) is to use a **Transfer Reversal** when the checkout.session.completed event is fired.

To use this approach with your Bubble app, you can create a Stripe Webhook that triggers a backend workflow whenever a Checkout Session is completed.

{% hint style="info" %}
This is an intermediate/advanced setup. Some familiarity with backend workflows and Stripe Webhooks is recommended.
{% endhint %}

You can implement this approach by following these steps:

1. **Enable backend workflows**

Navigate to Settings -> API and check the 'Enable Workflow API and backend workflows' option:

<figure><img src="https://445589018-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FOYowMYV4NApuJz7Z6lKJ%2Fuploads%2FyZdn4uohpm6yYZTo7Dsd%2FScreenshot%202025-02-25%20at%2011.59.38.png?alt=media&#x26;token=00a88d8b-1ca5-4763-8ad8-543b1d853561" alt=""><figcaption></figcaption></figure>

2. **Create a new backend workflow**

Navigate to the backend workflows tab and create a new API endpoint. It is recommended to use only lowercase letters and no spaces in the endpoint name.&#x20;

<figure><img src="https://445589018-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FOYowMYV4NApuJz7Z6lKJ%2Fuploads%2FyqYxDRZHYl2iWdbBGCU2%2FScreenshot%202025-02-25%20at%2012.01.58.png?alt=media&#x26;token=650119c0-edb9-46dc-ac52-2bfe002b0f72" alt=""><figcaption></figcaption></figure>

Change the **Parameter definition** field to **Detect request data** and click on the **Detect data** button. You should see a screen like the below appear:

<figure><img src="https://445589018-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FOYowMYV4NApuJz7Z6lKJ%2Fuploads%2Ff8yNIpeVNeKFBnrFam0H%2FScreenshot%202025-02-25%20at%2012.05.16.png?alt=media&#x26;token=1918d9e3-b161-4ab5-b83d-2826a399c71c" alt=""><figcaption></figcaption></figure>

Copy the URL presented to you to your clipboard.



3. **Create a webhook in your Stripe dashboard**

In your Stripe dashboard, navigate to:

Developers -> Webhooks&#x20;

and create a new endpoint. If you haven't previously created a webhook in Stripe, you may be presented with the option to 'Create an event destination' (click this). You'll then be presented with a screen that looks like this:

<figure><img src="https://445589018-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FOYowMYV4NApuJz7Z6lKJ%2Fuploads%2FIqOHNvcBJAVRaD113w3m%2FScreenshot%202025-02-25%20at%2012.11.27.png?alt=media&#x26;token=e2b735a5-85c3-4ea0-824d-444f3819272d" alt=""><figcaption></figcaption></figure>

Paste the URL you were presented with in step #2 into the **Endpoint URL** field.&#x20;

This is also a good time to add **authentication to your backend workflow**. When you initially created the API endpoint in your Bubble app, you may have noticed the 'This workflow can be run without authentication' option:

<figure><img src="https://445589018-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FOYowMYV4NApuJz7Z6lKJ%2Fuploads%2FI0BdaodrekZvZBHioCxU%2FScreenshot%202025-02-25%20at%2012.21.34.png?alt=media&#x26;token=b22e10b5-a780-438c-963d-fed521599964" alt=""><figcaption></figcaption></figure>

If you leave this unchecked (which it is recommended you do) the Stripe webhook will trigger the backend workflow unless we provide it with something that lets it prove to Bubble that it is authenticated.

You can do this by navigating to:

Settings -> API

and generating a new API token (you can name it whatever you want):

<figure><img src="https://445589018-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FOYowMYV4NApuJz7Z6lKJ%2Fuploads%2FpTVaFZzCC2MMP3jEPGtI%2FScreenshot%202025-02-25%20at%2012.24.00.png?alt=media&#x26;token=66bc789e-18eb-448b-9aad-7dcfc0dea57d" alt=""><figcaption></figcaption></figure>

{% hint style="info" %}
API tokens can be used to exploit your platform if they fall into the hands of a bad actor, so be very careful in using them.
{% endhint %}

Navigate back to your stripe dashboard and append the api\_token to the end of your endpoint URL (after adding a '?'):

<figure><img src="https://445589018-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FOYowMYV4NApuJz7Z6lKJ%2Fuploads%2FYcBIQDTr2DQuVyOOXX5J%2FScreenshot%202025-02-25%20at%2012.27.39.png?alt=media&#x26;token=a5d50f64-feba-4a69-b3f0-4f593ee11892" alt=""><figcaption></figcaption></figure>

Use the default 'Events on your account' for the **Listen to** option and choose checkout.session.completed for the event to listen to:

<figure><img src="https://445589018-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FOYowMYV4NApuJz7Z6lKJ%2Fuploads%2F44L704kYkiI2tZhY5vvG%2FScreenshot%202025-02-25%20at%2012.31.37.png?alt=media&#x26;token=63df8480-3fb3-41db-a140-87ceb56c46b0" alt=""><figcaption></figcaption></figure>

Click on add endpoint to create your Stripe Webhook.

4. **Initialize the webhook**

The webhook has now been created, but you still need to initialize it. To do this, ensure you have the **Detecting Request Data** popup open in your Bubble editor (it will not work otherwise):

<figure><img src="https://445589018-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FOYowMYV4NApuJz7Z6lKJ%2Fuploads%2Fi6beELQBFIOVBvodu97k%2FScreenshot%202025-02-25%20at%2012.37.45.png?alt=media&#x26;token=53dff001-5bb9-49d1-b2d8-c88b012c1161" alt=""><figcaption></figcaption></figure>

Then create a Checkout Session from your app and submit the payment form. Once the Checkout Session has been completed, navigate back to the backend workflows tab and you should see the data associated with the workflow coming through.

<figure><img src="https://445589018-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FOYowMYV4NApuJz7Z6lKJ%2Fuploads%2FtPOjaX9JQtDzB1TSugXf%2F2025-02-25%2012.41.12.gif?alt=media&#x26;token=31b48086-ae4a-4126-b684-33192954831a" alt=""><figcaption></figcaption></figure>

Most of the default field types Bubble assigns to the data are ok, but it is recommend you change the following fields from 'number' to 'date (UNIX)':

* created
* object created
* object expires\_at

5. **Remove 'initialize' from the endpoint URL in Stripe**

After successfully initlializing the webhook, you'll need to remove 'initialize' from the endpoint URL in Stripe:

<figure><img src="https://445589018-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FOYowMYV4NApuJz7Z6lKJ%2Fuploads%2FqXRVCAMW4i454KktwrB4%2F2025-02-25%2012.53.07.gif?alt=media&#x26;token=0cbc7d03-6e61-4a09-8db7-1098461902de" alt=""><figcaption></figcaption></figure>

Your backend workflow in Bubble should now be triggered whenever a Stripe Checkout Session is completed.

6. **Save down the payment intent ID associated with a transaction**

In order to transfer the tax component of a transaction back to the platform account, you can use the **Stripe Connect - Reverse a Transfer** action that comes with the Stripe Connect - Marketplace plugin. However, in order to run this action we'll first need to save down the amount of tax that was part of the transaction and also get the Transfer ID associated with the transaction.

It is recommended you start off by saving down the Payment Intent ID as this will be needed later.

{% hint style="info" %}
It is assumed you have added a 'transaction' data type to your database with the following fields.&#x20;

* Amount (cents) (number)
* Charge ID (text)
* Checkout Session ID (text)
* Payment Intent ID (text)
* Seller Account ID (text)
* Total Tax (number)
* Transfer ID (text)
{% endhint %}

Add a '**Make Changes to a Thing**' action to your backend workflow and 'Search for transactions: first item'. Add in a constraint so that you're searching for the first (and only) transaction in your database where the:&#x20;

Checkout Session ID = Request Data's object id

<figure><img src="https://445589018-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FOYowMYV4NApuJz7Z6lKJ%2Fuploads%2FrDGUN5wpfeH5WdibMEVI%2FScreenshot%202025-02-26%20at%2010.41.07.png?alt=media&#x26;token=e33a1597-7d9d-4881-9a4f-b944ab6fd3a7" alt=""><figcaption></figcaption></figure>

This will ensure you're updating the 'transaction' that was created for the Checkout Session whose completion triggered the backend workflow.

You can get the **Payment Intent ID** using the 'Stripe Connect - Retrieve Checkout Session Details' call that comes with the plugin.

<figure><img src="https://445589018-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FOYowMYV4NApuJz7Z6lKJ%2Fuploads%2FV3erzglnCZlM7ueaHKKa%2FScreenshot%202025-02-26%20at%2010.41.50.png?alt=media&#x26;token=eebc8dcc-9644-407e-ac2c-19ef6d36aba5" alt=""><figcaption></figcaption></figure>

7. **Save down the total tax associated with a transaction**

You can also get the Total Tax by using the 'Stripe Connect - Retrieve Checkout Session Details' call. However, it is strongly recommended to do this in a separate backend workflow that you then trigger from the first (that we used to get the Payment Intent ID).

{% hint style="info" %}
This approach is recommended as you'll need to get a few difference pieces of information and putting everything into separate backend workflows helps ensure all the data is available before the next action is triggered.
{% endhint %}

Create a new backend workflow called 'get\_tax\_component' (or similar) and pass through the checkout\_session\_id as a key:

<figure><img src="https://445589018-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FOYowMYV4NApuJz7Z6lKJ%2Fuploads%2FKMQl6U0g0lnj8YPAg9iE%2FScreenshot%202025-02-26%20at%2010.47.22.png?alt=media&#x26;token=29f7a799-81c5-4630-a34b-34ecc83ad6e2" alt=""><figcaption></figcaption></figure>

You can search for the correct transaction to update by using the 'checkout session id':

<figure><img src="https://445589018-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FOYowMYV4NApuJz7Z6lKJ%2Fuploads%2FD1ZV6bLRGjBBCZTsbCue%2FScreenshot%202025-02-26%20at%2012.05.23.png?alt=media&#x26;token=a0f47a08-aac5-4e13-8558-0682532f87c3" alt=""><figcaption></figcaption></figure>

Save down the total tax to your 'transaction' data type using the 'Stripe Connect - Retrieve Checkout Session Details' call. Use the **total\_details amount\_tax** value:

<figure><img src="https://445589018-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FOYowMYV4NApuJz7Z6lKJ%2Fuploads%2FY8C58IllpUh8KLsycrTz%2FScreenshot%202025-02-26%20at%2010.55.33.png?alt=media&#x26;token=736b0e3f-179b-4ffb-b10a-70864f62da72" alt=""><figcaption></figcaption></figure>

Go back to your first backend workflow and trigger the get\_tax\_amount backend workflow as the last step. Make sure to pass through the Checkout Session ID as a parameter:

<figure><img src="https://445589018-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FOYowMYV4NApuJz7Z6lKJ%2Fuploads%2FjOwox16B8iYKa4SriT4u%2FScreenshot%202025-02-26%20at%2011.11.23.png?alt=media&#x26;token=5ceca4e9-30a2-461d-a1cb-37487d5010d7" alt=""><figcaption></figcaption></figure>

8. **Save down the Charge ID associated with a transaction**

Create another backend workflow (call this one something like 'get\_charge\_id') and add a key called 'payment\_intent\_id':

<figure><img src="https://445589018-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FOYowMYV4NApuJz7Z6lKJ%2Fuploads%2FQvHhIRW9xzWbWPJnBv6D%2FScreenshot%202025-02-26%20at%2011.14.19.png?alt=media&#x26;token=230beb50-fca4-45d1-be43-e7c4ed5d8209" alt=""><figcaption></figcaption></figure>

You can search for the correct transaction entry to update using the 'payment\_intent\_id':

<figure><img src="https://445589018-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FOYowMYV4NApuJz7Z6lKJ%2Fuploads%2Fgyl5RmLnLdaB6jyWoBws%2FScreenshot%202025-02-26%20at%2012.07.00.png?alt=media&#x26;token=7f99bccb-aa47-41e8-a2af-fcad6898caad" alt=""><figcaption></figcaption></figure>

Save down the Charge ID to your 'transaction' data type using the 'Stripe Connect - Retrieve Payment Details' call. Use the **latest\_charge** value to get the Charge ID:

<figure><img src="https://445589018-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FOYowMYV4NApuJz7Z6lKJ%2Fuploads%2FPGuHmex6sh9NhAq6fgCV%2FScreenshot%202025-02-26%20at%2011.18.16.png?alt=media&#x26;token=153475d5-67ef-4864-927a-71c012427cd3" alt=""><figcaption></figcaption></figure>

Go back to your 'get\_tax\_component' backend workflow and trigger the get\_charge\_id backend workflow as the last step in it:

<figure><img src="https://445589018-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FOYowMYV4NApuJz7Z6lKJ%2Fuploads%2F3S7zBDXlX4lCU6w3kZLX%2FScreenshot%202025-02-26%20at%2011.20.07.png?alt=media&#x26;token=7b675499-0ca9-45d6-9b9e-a65107ae865c" alt=""><figcaption></figcaption></figure>

9. **Save down the Transfer ID associated with a transaction**

When you create a **Destination** charge with Stripe you automatically make a transfer from the platform account to the connected account. You'll need to get the ID associated with this transfer event as we'll be partially reversing this transfer to transfer the tax component back to the platform account.

Create another backend workflow called 'get\_transfer\_id' and add a key called 'charge\_id':

<figure><img src="https://445589018-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FOYowMYV4NApuJz7Z6lKJ%2Fuploads%2F8eV2fxxD2PtiP9VookWw%2FScreenshot%202025-02-26%20at%2011.22.40.png?alt=media&#x26;token=57f7b4fc-a6bf-4aab-8843-72d1d168834f" alt=""><figcaption></figcaption></figure>

You can use the Charge ID to search for the relevant transaction to update:

<figure><img src="https://445589018-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FOYowMYV4NApuJz7Z6lKJ%2Fuploads%2Fcduwq0y0cNHhrchnjGU2%2FScreenshot%202025-02-26%20at%2011.24.36.png?alt=media&#x26;token=653aadbd-19d2-4cb3-9768-c5a09e2aff42" alt=""><figcaption></figcaption></figure>

Save down the Transfer ID to your 'transaction' data type using the 'Stripe Connect - Retrieve Charge' call. Use the **transfer** value to get the Transfer ID:

<figure><img src="https://445589018-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FOYowMYV4NApuJz7Z6lKJ%2Fuploads%2Fxsu4rnRiIZrC1wte19ld%2FScreenshot%202025-02-26%20at%2011.25.25.png?alt=media&#x26;token=83892207-8f12-46b2-90cf-ebe0c0cd9dfc" alt=""><figcaption></figcaption></figure>

Go back to your 'get\_charge\_id' backend workflow and trigger the 'get\_transfer\_id' backend workflow as the last step in it:

<figure><img src="https://445589018-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FOYowMYV4NApuJz7Z6lKJ%2Fuploads%2FmeR56xPUdl05CUgzO2V3%2FScreenshot%202025-02-26%20at%2011.26.24.png?alt=media&#x26;token=e0130cfe-fd19-48fa-b2ca-6ea1a151123e" alt=""><figcaption></figcaption></figure>

10. **Transfer the tax component of the transaction to the platform account**

You now have all the necessary info to trigger the transfer reversal.

Create another backend workflow called 'reverse\_transfer'. This one will be used to transfer the tax to the platform account using the 'Stripe Connect - Reverse a Transfer' action. Add two 'keys' to this backend workflow:

* amount: this will represent the value of the tax to be transferred to the platform account
* transfer\_id: this will represent the Transfer ID&#x20;

<figure><img src="https://445589018-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FOYowMYV4NApuJz7Z6lKJ%2Fuploads%2FAenz0y18qOUJ9WvRY9p0%2FScreenshot%202025-02-26%20at%2011.28.24.png?alt=media&#x26;token=47586eef-f5c2-4b0a-b123-bee42b383486" alt=""><figcaption></figcaption></figure>

Add the 'Stripe Connect - Reverse a Transfer' action to the backend workflow:

<figure><img src="https://445589018-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FOYowMYV4NApuJz7Z6lKJ%2Fuploads%2FDJZ91IE3hMW3o7HjdgRN%2FScreenshot%202025-02-26%20at%2011.46.14.png?alt=media&#x26;token=850d88d0-7546-4ae5-86da-860fdea5e4a2" alt=""><figcaption></figcaption></figure>

Go back to your 'get\_transfer\_id' backend workflow and trigger the reverse\_transfer backend workflow as the last step in it:

<figure><img src="https://445589018-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FOYowMYV4NApuJz7Z6lKJ%2Fuploads%2FKEKifruODkiAmHCvkKX7%2FScreenshot%202025-02-26%20at%2011.55.49.png?alt=media&#x26;token=8e06cc50-bb8e-4fe2-9503-1b1a16eea8b6" alt=""><figcaption></figcaption></figure>

That's it! Now when you process a transaction where tax is collected with Stripe Tax, the tax should be automatically transferred back to your platform account. For example, in the below Checkout Session:&#x20;

* The product sold cost €100
* The platform fee was 20% (€20)
* The VAT (sales tax) charged was €18.70

<figure><img src="https://445589018-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FOYowMYV4NApuJz7Z6lKJ%2Fuploads%2FMztDrVinetI4jtPA5sDn%2FScreenshot%202025-02-25%20at%2014.59.46.png?alt=media&#x26;token=34522f28-e0a9-4c5f-914d-c38ca6290974" alt=""><figcaption></figcaption></figure>

After the payment has been made, the platform collects €20 as a platform fee (see the Transactions -> Collected fees section of the Stripe dashboard):

<figure><img src="https://445589018-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FOYowMYV4NApuJz7Z6lKJ%2Fuploads%2FNsdEGVzmTT3OBr1xNFbz%2FScreenshot%202025-02-26%20at%2012.01.59.png?alt=media&#x26;token=f1802d8d-2f5c-4743-a091-cbef52320cb3" alt=""><figcaption></figcaption></figure>

The €18.70 in tax is transferred back to the platform account as transfer reversal:

<figure><img src="https://445589018-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FOYowMYV4NApuJz7Z6lKJ%2Fuploads%2FECYdYA9CNJ1qwZsxwUBQ%2FScreenshot%202025-02-26%20at%2012.03.40.png?alt=media&#x26;token=e809700c-ed7e-4973-a41a-f02fde809e6d" alt=""><figcaption></figcaption></figure>

<figure><img src="https://445589018-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FOYowMYV4NApuJz7Z6lKJ%2Fuploads%2FdczXVQDzdHY0yfDrLJVC%2FScreenshot%202025-02-26%20at%2012.02.41.png?alt=media&#x26;token=73b94a8c-52ad-4930-9518-dad7f74f550c" alt=""><figcaption></figcaption></figure>



