Hey did you solve this? I have the exact same situation as you and the same problem
If you problem is similar to what I encountered, workflows will not run when the app is closed. Regardless of whether you tell it run every 1min, if bubble is not open, it will not run.
I solved a similar problem using the zapier plugin which may help you to take a look at. I’m not sure if they allow you to trigger by the minute as I was only looking at once per day, but it’s definitely something you should take a look at. With zapier, you can create a webhook that you can schedule to trigger every day in my case, and as it is hosted by a third party will run even if bubble is closed.
You can then in bubble create an API workflow triggered every time the zapier webhook goes off that will probably say something like ‘get data from current user’s google calendar’ and update it. Zapier integrates with plenty of plugins itself so you’ll hopefully find some luck with that, if not their customer support is pretty great so chuck them a message.
Hopefully this helps you out
Didn’t realise how long ago the first post was created, maybe my other post on the thread will help you instead if you’re struggling with the same problem
I haven’t drilled into Google Calendar yet, but I’ve spent a fair amount with Google Contacts, and I believe the authentication process is the same. I manage this on my own using the api connector, rather than using the login with social approach.
Google provides a refresh token and an expiration value, along with the access token. You can use the refresh token to automatically get a new access token, so you should be able to connect without the user. I store these values on the user item, and if at the time of access I see the token needs a refresh, I go get a new access token and store the new values before performing the actual api call.
It’s important to note that the refresh token is only provided to you when the user originally grants oauth permission. If you don’t capture this, and save for later, you have no way to get it going forward ( I don’t believe.) In testing I had to often gone up to my google account permissions and revoked the permission I granted, so that a new authentication process will be triggered, and I could get the refresh token.
This seems like an excellent approach! Please may you show us exactly how you did this with screenshots? I’m sorry to ask and I know it takes some of your time, but I think many people would really appreciate it.
All the best,
Google App creation
You first need to create an OAuth client app in the Google Console at https://console.cloud.google.com and navigate to “Credentials” in the API section.
Click on “Create credentials” and choose “OAuth client ID”
Select “Web application”, provide a name (not terribly important what it is,) and provide the “Authorized redirect URI’s” for you bubble app. These need to match a page created later in this process, so for this example we are going to add three URI’s:
https://app.mydomain.com/temp_catch_token (only used during setup)
Click “Create” and you will be given your client id and secret:
Your app on the google console is now created, but has not been verified. You can continue testing at this point, but will want to look into the process of verifying your app with Google to prevent your users having to see an unverified message. I’m not covering the verification process here as that is well documented online.
You are going to be storing some values from Google, so you’ll need to setup fields based on your needs. I’m going to illustrate how to simply store these on the Bubble User record. In Bubble, navigate to Data > Data Types > User and create three new fields:
google access_expire_datetime (date)
Temp Setup Page
To get our API’s initialized, we are going to need a temporary page so we can get a code.
In Bubble, create a page named: temp_catch_token. It doesn’t need anything else, it just has to exist,
In Bubble, navigate to Plugins and add the “API Connector” plugin from Bubble.
Click “Add another API” and name this group of API calls. I’m using “GOOG” Authentication should be set to “None of self-handled”
Now we will create a few API calls that will be utilized: two to manage token’s and a sample call to retrieve contacts. After you have the authentication in place, you can simply add more calls to meet your needs.
For each of these, make sure you are working in the GOOG API, and click “Add another CALL” (not “Add another API”.) To modify each call, click the “expand” link on the right side of the call you are working on.
Google Access Token
First up is “Access Token” and should be setup as shown below. The exact key names and values needed are:
code = the code Google provides after hitting the auth url (we’ll get this for testing below)
client_id = the client id provided by google in app creation
client_secret = the client secret provided by google in app creation
redirect_uri = https://app.mydomain.com/version-test/temp_catch_token
grant_type = authorization_code
The items with the “Private” checkbox unchecked, are items that you will be able to dynamically set during your workflows. They all will be set to Querystring. Both type’s are set to JSON and this will be used as an “Action”. To “Initialize the call” we need to manually go through the auth process with Google and use the temp page we created. You can do this by going to:
Replace MY_CLIENT_ID with your google app client id. What should happen, is you will go through authorization with google, and get returned to your temp token catcher where you can grab the code from the url string in your browser window:
You need to select everything after the ?code=
and copy/paste it into the value for code in the Access Token api call. These codes do expire, and I believe are single use, so if you don’t act fast, just try again.
With all the values in place, you can Initialize the call. If it works, you will get something like this:
Click on the Show Raw data link, and Copy / Paste the values you received for later use.
This call is now ready for use!
Google Refresh Token
Next up is “Refresh GOOG token”. As with before, use the values shown below, being careful to check all the settings. The exact keys and values are:
refresh_token = the value of refresh_token you just got when initializing Google Access Token
client_id = client id from the google app you created
client_secret = client secret from the google app you created
grant_type = refresh_token
You can now Initialize this call, and again, save the values from the raw data.
This call is now ready for use!
Sample API Call - Get Contacts
And lastly, our sample call to see everything work, “Get GOOG Contacts”. For this one, you need to click “Add header” and add a key with name “Authorization” (no quotes used). The value of Authorization is the word Bearer, followed by a space, followed by an Access Token (not refresh token.) Such as:
For the parameter keys we have (these are specific to what I’m doing, your calls will be different):
alt = json
max-results = 20 (just for testing, can be dynamically set in production)
v = 3
start-index = 1
Time to Initial This Call. If this works, you’ll get a similar screen as the others, but no need to save anything.
Now we are going to setup the workflow for authentication. Create a button on a bubble page and create a workflow event for when it is clicked. The important action item is to “Open an external website” which is found under the Navigation group. You can see in the screenshot that I am actually looking up the values in a database, but you may not need that.
Here is the actual Destination that I build:
If you dynamically build this, consider that you might have to URL encode values.
A few notes on the values above:
If I recall correctly, scope is a comma separated list of values, so you can do multiple scopes in one auth. MY_CLIENT_ID is the client id you were given by Google when creating your app. If you forgot to save this, you can go back to Google console, get to API credentials, and find the client id and secret. The value for redirect_uri must match one of the Authorized redirect URI’s in your Google app. For testing, it will have version-test.
This is a page created in Bubble that Google redirects to after a user grants permission. It basically “catches” the code that is sent over by Google, triggers further processing, and redirects the user to a final page.
Here’s what mine does:
I show a popup screen with a spinner to indicate to the user that we are processing the request (Steps 1 and 2 below.) I then take the code provided by Google and go get an Access token, and store what is given to me on the user record. Lastly, I usually redirect to another page, or perform some action (like import contacts.)
Catching the code and getting a token is done by grabbing a url parameter and using the “Access Token” call we created in the GOOG api:
The value for “(parameter) code” is captured like this:
The value for “(paramter) redirect_uri” is a url string. For this demo it would be https://app.mydomain.com/version-test/catch_token
Now it is time to save to our db, what Google provides:
You can see that I’m actually also saving the token type from Google, but this is currently always “Bearer” so not exactly required. The three you need are the access_token, the refresh_token and the datetime of expiration. These all come from the previous step as shown. Google provides expiration as a number of seconds from when it generated the token, so I chose to store that as the current date/time plus the seconds they provide, giving me the datetime of when the token will no longer be valid.
We refresh the access token, using the refresh token. Anytime I’m about to make an api call, I check the user record to see if the token has expired. If it has, I trigger a refresh, if not, I just hit the actual api call.
The refresh is an action calling the Refresh GOOG token api call we created earlier. You pass the refresh token, and you get back a new access token and new expiration. Save the access token and expiration as you did in the original auth, overwriting the current values. The refresh token never changes.
One note, in testing if you don’t properly save the refresh token, you will never see it again. To get around that, simple go to https://myaccount.google.com/permissions and revoke access to your app.
The actual API call
Now, you are ready to make calls when you need data or take action. Using the sample above as a guide, just create calls that always have the authorization header, but then have the unique parameters and url you need. I think the hard part is the auth, so I’m just stopping here!!!
I have not really proof-read this, so please let me know if you run into trouble–it is very likely I missed something. I’ll be happy to edit so everybody has a good set of instructions in the future.
Quick (well probably not quick) question: Have you worked out a decent way to keep tokens alive in production (live) environment? This seems to be handled automagically by Bubble if one is using a “standard” OAuth “User-Agent” flow. But how to do this on one’s own?
Do you have thoughts on that? This is a frustrating issue with complex APIs like DocuSign where there’s no built in Bubble auth scheme that exactly matches…
BTW, this post about Google Calendar helped me immensely with APIs like this that require what I call a certain amount of bootstrapping to get running.
I’ve developed some more automated methods to do this stuff that I’ll be sharing shortly… However, token management is still problematic and AFAICT can’t quite be bundled up into a Bubble “plug-in” type thing…
Sounds like you are asking more broadly than Google, but the instructions above do allow me to keep tokens alive in production. Once I have the refresh token stored in the db, a new access token is an api call away. Other providers don’t expire the access token, so those are alive until revoked. I have a few webhooks coming in, which I respond to by retrieving data for the user without their involvement, using the tokens I’ve stored.
The standard Oauth using social login just didn’t work for me – I like to keep app login separate from server connections so when those overlapped it became problematic. The external sources I’ve dealt with have been similar enough that I don’t feel like I am re-inventing the wheel each time. It’s always kinda the same – create an app with the provider, store the client id and secret, trigger an auth, get a code, exchange for a token, store it, use it, rinse, repeat…kinda. I took a quick peak at the Docusign api, and it looks to operate very closely to the Google style – once you have a refresh token, you can exchange for access whenever it is expired.
I feel like I might be missing your question though…happy to keep the dialog open, just rephrase for me.
I haven’t looked at the developer side of plugins so I can’t provide any help there!
Glad the post helped you…
Thanks for the reply! This might explain my question a bit better: While I can manually refresh the token, if it expires and is not refreshed before expiry, one has to go through the entire “bootstrap” process again. (I’m storing the token using pretty much exactly the approach you’ve suggested. However, I just create a top-level “thing” that represents the token and includes token, refresh token, expiry time, Authentication Header, etc.)
So basically what needs to be done (and what I assume Bubble does in their own token management) is – when a token gets refreshed and has a new expiry time – to schedule an event to refresh the token some short time before token expiry.
This would be simple (using scheduled workflows) except that workflows cannot call themselves (to prevent runaway loop conditions). Since (for example), DocuSign tokens last for 8 hours, we don’t need a huge number of scheduled events (which are just firing off pretty lightweight workflows anyway) – basically we need to “click a virtual button” slightly more than 4 times per day.
There’s seemingly no way to do this in Bubble at the moment on a lower-tier plan, even though this ability is seemingly provided to all plans. (That is, anytime, you can take advantage of a “built-in” OAuth 2 API authentication flow, it would seem that Bubble is automagically scheduling such refresh events for you. However, if you have to manage it yourself, you have no way of reliably ensuring tokens do not expire from within the Bubble ecosystem itself. This is a drag.)
When I’d asked my previous question, I was not familiar yet with scheduled and recurring events in Bubble. But, having looked at it, it would seem that one is basically “stuck” if you wanted to do this all self-contained.
I suppose the work-around is to have some external system make an API call to one’s app every not-quite-8-hours to refresh the token. (Of course this is sort of absurd because that still puts the same load on your Bubble app as it would if the scheduling could be done internally in the recursive way that we are not, at present, allowed to do.)
Anyway, thanks a bazillion once again for your insights around the Google Calendar API stuff – really helped me understand the API Connector better!
Ahhh…so that is the difference. A Google refresh token does not expire, you simply send it in and get a new access token in return. Docusign is dealing with signatures, so I suspect that is their reasoning behind expiration…their business is wrapped up in guarantees of people being present.
Sounds like you have researched this up one side, and down the other, but just in case…this caught my attention:
It looks like they have a separate “service” integration that allows you to get a JWT Token that only requires a user grant access one time – perhaps this meets your need?
Hey Ken: very good thought but that Auth scheme is not for web/mobile app use cases. There’s nothing wrong really with what DS and other APIs do, but the lack of flexibility on the bubble side is a drag.
(Kind of my other point is, it’s be great if there was a way to just insert ourselves into the Bubble token management system. Like, (1) we’re bootstrapped (2) here are the calls to refresh the token (3) keep it alive now, yo! )
I love how the API Connector is pretty much doofus proof for simple use cases, but it quickly gets in the way in complex situations…
Though back to DS for a moment, it does seem like what their API really “wants” is for users to be created and log in via DS in a “User-Agent” type workflow (which again Bubble can’t quite do because the account and email GETs are not exposed as simple paths, but returned in an array — it’s a bit crazy-making.)
Since DS is my e-sign of choice, I can see needing this myself in the future. Help me understand why the service integration won’t work…it seems like it is for this exact use case. Like I said, you’ve spent way more than the 10 minutes I did browsing, so I’m looking to learn what I’m missing.
I think the issue is that such integrations are where the app represents the organization. If your application is, for example, sending an agreement to users (such as a stock grant document that an employee user is to view and sign), that’s OK.
However, let’s say you’re trying to do what I’m trying to do: Offer end users (who are merely users of my platform) a convenient way to get THEIR agreements (like a rental agreement in my case) signed by either another system user or an outside partly, the Service Integration is NOT appropriate.
You’re still kinda “stuck” in Bubble for the moment as User-Agent workflow can not successfully be established as far as I can tell. I have a nifty template about this, Ken, that perhaps you’d like to explore. Maybe you’ll see a way to get it working right. See my other post about DocuSign — I basically took your Google Bootstrap idea and made it more of its own little helper app…
And that sample app is here: https://docusign-v2-api-helper.bubbleapps.io/
(It’s also in the Templates marketplace. Not sure if that’s where you need to go to find an editable version… Bubble website is weird to me sometimes…)
Google doesn’t return a refresh token with the Access Token call. Any help?
Hey @mebeingken, thanks for the excellent explanation! Quick question: Following your steps, I understand I could read the subject or body of an email that a user receives. However, is it possible to extract such subject/body of an email when an email with a specific subject is received? This is something that I’m currently doing through Zapier (I guess they keep some webhooks open to make sure that a process is triggered every time a email with a specific subject arrives), but I’m not sure if it can be replicated through Bubble with the API process you’ve explained. What do you think? Thanks a lot!
You are so awesome, thank you
Thank you for the explanation! That was very helpful. However, I’m lost at the last step where the Token needs to be refreshed. I added the Refresh Token steps at the end of “When page is loaded…” after the access token and the refresh token are saved to the user database, but the token is not being refreshed, and I still see the same first access token in my user database.
Could you please give more details on how to trigger the refresh token properly?
Hi @mebeingken ,
I could create the API calls and get both access + refresh tokens during the process. However when I try to build a workflow I can never get a refresh token in my database - not sure what I am doing wrong. Could you please check if the steps below make sense:
Revoke all google permissions through https://myaccount.google.com/permissions
Create a page with a button to “connect to google” - this will open the external website (google url that will lead the user through the confirmations to allow access and will redirect to our “catch token” page.
The “catch token” page will run the Access Token API call - parameter code will come from its own URL and parameter redirect_uri will be any other page - the workflow in this page will also update the user’s access token, refresh token and expiration (database) based on the results of the Access Token API call.
Following the above, I can never get a “Refresh Token” in my users database - it’s blank. I have an access token, but no refresh token. And it’s funny because my access token works - I just can never refresh it, of course, as I don’t have a refresh token.
These are my API calls:
Edit: I think I know what’s happening. I just tested now with a new user (different from the user I was using for the API setup) and it worked - I could get a refresh token. So I think google will not send you a refresh token more than once? But I made sure I had revoked all permissions through my google account etc…that’s odd I would like to understand the rule for the refresh token to be sent.
On the above, was able to fix it. I think I was trying to get “new” refresh tokens too soon - it seems Google won’t share a new refresh token instantly after you revoke access for some reason…