Flag of Ukraine
SymfonyCasts stands united with the people of Ukraine
This course is archived!
This tutorial uses an older version of Symfony of the stripe-php SDK. The majority of the concepts are still valid, though there *are* differences. We've done our best to add notes & comments that describe these changes.

Stripe Events & Webhooks

Keep on Learning!

If you liked what you've learned so far, dive in!
Subscribe to get access to this tutorial plus
video, code and script downloads.

Start your All-Access Pass
Buy just this tutorial for $12.00

With a Subscription, click any sentence in the script to jump to that part of the video!

Login Subscribe

Oh boy, it's finally time to talk about the big subject I've been avoiding: webhooks. You see, every month Stripe is going to try to renew each customer's subscription. If it fails to charge their card, you might want to send them an email. If it's successful, you might want to send them a receipt. Or, if it fails so many times that the subscription needs to be cancelled, then we need to update our database to reflect that.

In short, Stripe needs to communicate back to us when certain things, or events happen.

Stripe Events

Go to our Customer page in Stripe. At the bottom, you'll see an events section. Basically, whenever anything happens in Stripe, an event is created. For example, when we added a new card, there was an event whose type field is set to customer.source.created. Every action becomes an event and there are many different types of events for the many things that can happen.

In fact, switch over to Stripe's API docs. Event is so important that it's an object in the API: you can list and fetch them. Click Types of events. Awesome! A big, giant, beautiful list of all the different event types so you can figure out what each event means.

What Happens when we Can't Charge a User?

Out of this list, there are just a few types that you'll need to worry about. The first is the event type that occurs when the customer's subscription is canceled when Stripe can't charge their card for renewal.

So, what actually happens when Stripe can't charge a card? Go back to the Stripe Dashboard and go to "Account Settings", and then "Subscriptions". This screen is really important: it determines exactly what happens when a card can't be charged. By default, Stripe will attempt to charge the card once, then try again 3, 5 and 7 days later. If it still can't charge the card, it will finally cancel the subscription. You can tweak the timing, but the story is always this: Stripe tries to charge a few times, then eventually cancels the subscription.

Hello Webhooks & requestb.in

When this happens, we need Stripe to tell us that the subscription was canceled. And we'll do this via a webhook. It's pretty simple: we configure a webhook URL to our site in Stripe. Then, whenever certain event types happen, Stripe will send a request to our URL that contains the event object as JSON. So if Stripe sent us a webhook whenever a subscription was canceled, we would be in business!

A really nice way to test out webhooks is by using a site called http://requestb.in.


The http://requestb.in site is no longer available (see https://github.com/Runscope/requestbin#readme). Try https://requestbin.com instead that has a bunch more features.

Click "Create a RequestBin". Ok, real simple: this page will record and display any requests made to this temporary URL.

Back on our dashboard, add a webhook and paste the URL. Put it in test mode, so that we only receive events from the "test" environment. Next, click "Select events". Instead of receiving all event types, let's just choose the few we want. For now, that's just customer.subscription.deleted.

Yep, this is the event that happens when a subscription is cancelled, for any reason, including when a user's card couldn't be charged.

Create that webhook! Ok, let's see what a test webhook looks like. Click "Send test webhook" and change the event to customer.subscription.deleted. Now, send that webhook!

Refresh the RequestBin page. So cool! This shows us the raw JSON request body that was just sent to us. These events are just objects in Stripe's API, like Customer, Subscription or anything else. But if you configure a webhook, then Stripe will send that event to us, instead of us needing to fetch it from them.

Here's the next goal: setup a route and controller on our site that's capable of handling webhooks and doing different things in our system when different event types happen.

Leave a comment!

Login or Register to join the conversation
Default user avatar
Default user avatar Blueblazer172 | posted 5 years ago


One last thing: i want to let the users register and i got the awful basic template from FOSuserbundle, but how can i change the style to bootstrap design? like you did for the login. Is there an easy way to overwrite te basic style, cause i searched the web and there was no clear information about that.
what do i have to do to chage the styling ?
when i copy the code from login.html.twig and paste it into the Registration/register_content.html.twig it actually shows me the bootstrap style and i can change the form groups to username, email, password and repeat password, but i have no _csrf_token and no errors variable, it says there is no variable like that.
I know that in the RegistrationController is the logic behind the form and i also know where to pass the eroor and csrf token: In the last array from the render return i have to setup the variable, but how can i catch an error there? or the csrf token? or am i going into the wrong direction? is there an easier way of doing that?

Thanks a lot :)


Yo Blueblazer172!

Ok, so there are basically 2 things you need to know about styling the FOSUserBundle templates. And btw, the templates are horribly styled by design - the idea is that you will definitely need to override them... so they're made plain.

1) Most of what you see on the registration page are the form fields. How the form fields are rendered is based on your form theme. Make sure you've configured the bootstrap form theme: http://knpuniversity.com/screencast/symfony-forms/render-form-bootstrap

2) You will also need to override the layout file used by the bundle. The docs are here: https://symfony.com/doc/master/bundles/FOSUserBundle/overriding_templates.html#example-overriding-the-default-layout-html-twig. But, the docs are incomplete! The directions for how to override this are correct, but the example layout.html.twig template they have is wrong. It should be:

{% extends 'base.html.twig' %}

{% block body %}
    {% block fos_user_content %}{% endblock %}
{% endblock %}

Basically, FOSUserBundle doesn't even know that your bundle has a base.html.twig base layout file. So, instead, all of its templates extend this layout.html.twig... which has almost not styling on it. LAME! To make FOSUserBundle's templates use base.html.twig, you basically override their layout file, and make sure that the contents of the fos_user_content block (this is the block where all of the FOSUserBundle's templates put their content) is rendered in the body block area (which is usually the name of the block in base.html.twig where our content goes). The tl;dr of this step is that the registration page will now start using your base layout... which is important because without this, your bootstrap.css file is never included.

You may also want to override the individual templates. For example, to override the registration template, you can override this template: https://github.com/FriendsOfSymfony/FOSUserBundle/blob/master/Resources/views/Registration/register_content.html.twig

If you have more questions about this - let me know! I'm giving you the 80% explanation - not sure what things you do or don't have experience in (e.g. forms, etc) :)


Default user avatar
Default user avatar Blueblazer172 | weaverryan | posted 5 years ago

I've successfully figured it out during today, but thanks anyways :)

but there is one little thing that does not work...
i loop through every error for each field. that could be done in one for loop. but i don't know. so here is the register.html.twig file: http://pastebin.com/6H5w51qn

what do i have to change?


Hey Blueblazer172
You can simplify all of this work, if you just render the whole form like this

{{ form_start(form, {'attr': {...} }) }}
    {{ form_widget(form) }}
    {{ form_end(form) }}

or you can render field by field using the form_row(form.field) function

You might find this tutorial very helpful:

Have a nice day!

Default user avatar
Default user avatar Blueblazer172 | MolloKhan | posted 5 years ago | edited

thanks :)

but how can i simplify this code:

<div class="alert alert-danger">

                        {% for errorItem in form.username.vars.errors %}

                            <li>{{ errorItem.message }}</li>

                        {% endfor %}

                        {% for errorItem in form.email.vars.errors %}

                            <li>{{ errorItem.message }}</li>

                        {% endfor %}

                        {% for errorItem in form.plainPassword.first.vars.errors %}

                            <li>{{ errorItem.message }}</li>

                        {% endfor %}

                        {% for errorItem in form.plainPassword.second.vars.errors %}

                            <li>{{ errorItem.message }}</li>

                        {% endfor %}

Hey Blueblazer172

If you only want to render field errors, you can do something like this
{{ form_errors(form.field) }}
for every field you want to render


1 Reply
Default user avatar
Default user avatar Blueblazer172 | MolloKhan | posted 5 years ago

thanks you are amazing :)) finally it is working as i want to have it :)
best english :)


I'm glad to hear you could achieved it :)

If you have more questions, don't hesitate to contact us

Cat in space

"Houston: no signs of life"
Start the conversation!

This tutorial uses an older version of Symfony of the stripe-php SDK. The majority of the concepts are still valid, though there *are* differences. We've done our best to add notes & comments that describe these changes.

What PHP libraries does this tutorial use?

// composer.json
    "require": {
        "php": ">=5.5.9, <7.4",
        "symfony/symfony": "3.1.*", // v3.1.10
        "doctrine/orm": "^2.5", // v2.7.2
        "doctrine/doctrine-bundle": "^1.6", // 1.6.8
        "doctrine/doctrine-cache-bundle": "^1.2", // 1.3.0
        "symfony/swiftmailer-bundle": "^2.3", // v2.6.2
        "symfony/monolog-bundle": "^2.8", // v2.12.1
        "symfony/polyfill-apcu": "^1.0", // v1.3.0
        "sensio/distribution-bundle": "^5.0", // v5.0.22
        "sensio/framework-extra-bundle": "^3.0.2", // v3.0.26
        "incenteev/composer-parameter-handler": "^2.0", // v2.1.2
        "friendsofsymfony/user-bundle": "~2.0.1", // v2.0.1
        "stof/doctrine-extensions-bundle": "^1.2", // v1.2.2
        "stripe/stripe-php": "^3.15", // v3.23.0
        "doctrine/doctrine-migrations-bundle": "^1.1", // v1.2.1
        "phpunit/phpunit": "^5.5", // 5.7.20
        "composer/package-versions-deprecated": "^1.11" // 1.11.99
    "require-dev": {
        "sensio/generator-bundle": "^3.0", // v3.1.4
        "symfony/phpunit-bridge": "^3.0", // v3.3.0
        "hautelook/alice-bundle": "^1.3", // v1.4.1
        "doctrine/data-fixtures": "^1.2" // v1.2.2