App Uninstalled Webhook0:00
When user uninstalls our app, we should probably remove their user account and do some cleanup. This is where webhooks can help. Webhooks basically allow us to hook into certain events or actions to keep our app in sync with Shopify. In addition to user uninstalling event, there are many other events, or rather topics as Shopify calls them, that we could hook into and listen on, such as hooks related to products, orders, customers, and so on. List of these event topics are available within the API documentation, and the first one, as you can see, is the app uninstalled topic. Laravel Shopify actually provides a job for the app uninstalled webhook that we could use to automatically source delete the shop and its charges. So let's open our code editor. Let's open the terminal. We'll run vendor/bin/sail artisan vendor:publish --tag=shopify-jobs. This is going to publish that app uninstalled job. So let's open
Register Webhook in Config0:44
So let's open our code editor. Let's open the terminal. We'll run vendor/bin/sail artisan vendor:publish --tag=shopify.jobs. This is going to publish that appUninstalledJob. So let's open that job. And as you can see, this job simply extends the AppUninstalledJob from the Laravel Shopify package. So let's open that. And this job basically finds the User record by using the domain or the name, and then it does some cleanup and soft deletes the User as well as its charges. Now, in order for this to be triggered, we need to register it or enable it within the ShopifyAppConfig. So if we go to ShopifyAppConfig, we see a section for the webhooks, and it even has some examples that we could use. So let's copy one of the examples, paste it in here. And as you can see, the first element is the topic that we need to provide, and then the address. The topic for our case will be app_uninstalled. So we need to replace this with something like app_uninstalled.
Webhook Env Configuration1:37
the first element is the topic that we need to provide, and then the address. The topic for our case will be app_uninstalled. So we need to replace this with something like app_uninstalled. But I don't want to hard code it here. Instead, I'm going to create environment variables. So let me remove it from here. We'll take this, open the .env file, scroll down, and we'll add these variables in here. So the topic will be app/slash/uninstalled. And then the address is going to be something like this. The address is basically your app's URL /webhook/ slash the topic name, whichever that is. So orders/create, or in our case, that is app-dash-uninstalled. Now, as far as the URL goes, I'm using ngrok, so I'm going to put in my ngrok URL. Now note that in Laravel Shopify documentation, as well as within the Shopify documentation itself, you might see all uppercase underscore syntax for the topic names. Even within the Shopify app config examples,
Shopify documentation, as well as within the Shopify documentation itself, you might see all uppercase underscore syntax for the topic names. Even within the Shopify app config examples, we have all uppercase underscore syntax. But in our .env file, I put it as all lowercase slash syntax. Both are actually valid, and both will work. Lowercase slash syntax is for REST API, and the all uppercase underscore is for GraphQL API. Laravel Shopify behind the scenes actually converts the lowercase slash syntax into the GraphQL syntax because it uses the GraphQL mutation to create these webhooks. That being said, though, I would still advise to use the GraphQL syntax for the topic names just in case whenever you're creating custom webhooks to avoid some issues later on. So instead of the lowercase slash, we're going to do all uppercase underscore syntax. So that's app_uninstalled. All right, so let's test this out. I already have my QWorker
Test Uninstall Cleanup3:14
some issues later on. So instead of the lowercase slash, we're going to do all uppercase underscore syntax. So that's app uninstalled. All right, so let's test this out. I already have my QWorker running, so we're going to uninstall the app and see if our User gets soft deleted. So let's open the browser. We'll click on settings. We'll go to apps and sales channels, and we'll click on uninstall in here. The app has been uninstalled successfully. Let's switch to telescope, and we see that the app uninstalled job has been automatically successfully dispatched and processed. Now let's go back to the editor. Let's open the users table. Let's check the deleted_at column. And sure enough, we see that the User record has been soft deleted. Great. So now that we know a little bit about the webhooks, let's talk a bit about GDPR and mandatory webhooks. GDPR, if you don't know, stands for General Data Protection Regulation, and it's basically a set
GDPR Mandatory Webhooks4:04
we know a little bit about the webhooks, let's talk a bit about GDPR and mandatory webhooks. GDPR, if you don't know, stands for General Data Protection Regulation, and it's basically a set of requirements for businesses or entities that deal with personal information. Shopify enforces this and requires all apps to be GDPR compliant, even if the app currently does not collect any personal data. If you don't implement the mandatory webhooks or don't respond as required by Shopify, there is a good chance that your app will be rejected when you submit it for review. Now there are three mandatory webhooks. We have customers/data_request, which basically requests to view stored customer data. Then we have customers/redact, which is a request to delete customer data. And we have shop/redact, which is a request to delete all the shop data. The documentation here provides a lot of details on what the response of webhooks should look like,
customer data. And we have shopRedact, which is a request to delete all the shop data. The documentation here provides a lot of details on what the response of webhooks should look like, how to implement it, and so on. And Laravel Shopify also has a section on it within its documentation, so check it out before submitting your apps for review. So let's implement these mandatory webhooks one by one. We're going to do the shopRedact first. As stated here, 48 hours after the user uninstalls the app, Shopify will send a payload to the shopRedact topic. This payload includes the shopId and the domain, which can be used to basically look up the proper user or shop record. Now using that information, you should basically delete all the shop and customer information that you might be storing in your database. All right, so let's set this up. We're going to use a command provided by Laravel Shopify to create webhook jobs. So let's go back to Code.
Create Shop Redact Job5:36
information that you might be storing in your database. All right, so let's set this up. We're going to use a command provided by Laravel Shopify to create webhook jobs. So let's go back to Code Editor. Let's open the terminal. We'll do vendor/bin/sail artisan Shopify:app make:webhook. We'll call this ShopRedactJob. And the topic is shop/redact. Hit enter. And as you can see, it prints this config option that we can copy and paste it into the webhooks option within the Shopify configuration. However, these three GDPR related mandatory hooks should not be registered within the Shopify app config file. They will automatically be handled behind the scenes. So make sure that you don't register these mandatory webhooks within the config file. You can use this same command to create the custom webhook jobs for other events, topics, and those you should register within the Shopify app config file, just like we did with the uninstalled webhook.
same command to create the custom webhook jobs for other events, topics, and those you should register within the Shopify app config file, just like we did with the uninstalled webhook. All right, so let's open the ShopRedactJob. So we'll open it here. And what we're going to do actually is that we'll go back to Laravel Shopify API documentation. This page contains some examples of these jobs. So we have this example for the ShopRedactJob. So we're going to copy this and paste it in here. We'll actually get rid of these things. Let's do a little bit of cleanup. I don't think we need any of that. We'll make these properties promoted. So we'll do private readonly and do the same thing here. And within the handle method, we see that it simply looks up the shop or the user record using the shop domain and then it does the delete. So let's actually fix this up a little bit. I'm going to import this and then we'll do if we have the shop only
Set GDPR Webhook Endpoints7:14
the shop or the User record using the shop domain and then it does the delete. So let's actually fix this up a little bit. I'm going to import this and then we'll do if we have the shop only then we'll delete and we'll actually replace this with a rescue helper like this and that will do the logging for us. Great. So the next step is now to activate this webhook, which can be done through the partner's dashboard. So let's open the partner dashboard up setup page, scroll down and we see a section for GDPR mandatory webhooks. As you can see, we have three input fields here for three endpoints for those three mandatory hooks. Again, I'm using ngrok so I'm going to paste in the ngrok URL. Then we'll have /webhook/shop/redact and this actually goes into this third input field and we'll copy it and paste it into the other ones as well. This is going to be customers data request and this will be customers redact. Let's save this and note that
Create Customer GDPR Jobs8:05
into this third input field and we'll copy it and paste it into the other ones as well. This is going to be customersDataRequest and this will be customersRedact. Let's save this and note that Laravel Shopify basically takes care of the mapping between this route and which job to execute behind the scenes. It knows this by splitting this endpoint by dash and then uppercasing the first letter of each word and appending the job at the end. So this essentially becomes ShopRedactJob and this is the job that it dispatches when the request comes in for this endpoint. And same goes for the customersRedact and customersDataRequest. So we need to create jobs for these two endpoints. So let's go back to code. We're actually going to duplicate the ShopRedactJob. So let's duplicate this. We'll call it CustomersRedactJob. This request basically will receive the resource IDs within the payload which needs to be deleted. So if you have access to the shop's customers and
this. We'll call it CustomersRedactJob. This request basically will receive the resource IDs within the payload which needs to be deleted. So if you have access to the shop's customers and orders and you're storing some of their information in the database you should delete them when you get this request. Now in our case we only store the IDs and nothing else so we don't really need to do anything in this job. So I'm just going to leave it as an empty job so that it responds with 200 OK. Let's now duplicate this and create the final mandatory webhook job. So we'll call it CustomersDataRequestJob. And again we don't store any customer information other than the ID so we can just leave it blank as well. So a response with 200 OK. All right. I think we're pretty much done here. It's time to deploy our app to production. We're going to do that in the next episode but to prepare for it we should probably move the faker dependency from the
Prep for Deployment9:47
pretty much done here. It's time to deploy our app to production. We're going to do that in the next episode but to prepare for it we should probably move the faker dependency from the require-dev to just require. So let's open the composer.json and we'll move the faker from here to here. Now the reason we're doing this is because in production when the composer install is run it is going to be run with no dev option to not install dev dependencies in production. But our app needs the faker because we use it to generate some of the fake names for the customers. So with that out of the way let's move on to the deployment in the next episode.
