Further workflow is being triggered, but the list to run on looks like is empty.
I have webhook logs entry in my db when I’m receiving email, but no db email record gets created, because of this empty list.
This is the additional filtering for the Gmail Get history endpoint:
I feel your pain—the Gmail API is a total beast when it comes to nested data.
Looking at your raw JSON, the issue is that messages (or messagesAdded) is an array inside another array (history). Bubble is likely getting confused trying to “flatten” that list of lists, which is why it’s coming up empty when you try to run the workflow on it.
Here’s what I’d check first:
The “List of Lists” Problem: Bubble often struggles to aggregate nested arrays into one flat list for a “Schedule on a list” action. Try a quick test: change the “List to run on” to just Result of step 1’s history:first item’s messagesAdded. If that works, you know the data is there, but Bubble just can’t “see” the full combined list.
Re-initialize with a “dirty” response: Sometimes the API Connector maps these types weirdly. Go back to the plugin settings, re-initialize, and make sure messagesAdded is actually recognized as a list of objects.
The Regex Workaround: If Bubble keeps being stubborn with the mapping, a “hacky” but solid fix is to take the Raw Body Text and use a Regex pattern to extract all the Message IDs into a simple list of strings. It’s way less headache than fighting Bubble’s nested data logic.
@ekpereogheneogaga2020 I have implemented oauth for gmail and i’m manually storing access and refresh token. Currently the refresh is triggered by the user when clicks on a button, but i was thinking about implementic automatic refresh every 1 hour, just to make sure the webhook works all the time (in addition to the renew watch). Do you think it makes sense to have the refresh every 1 hour or i should limit it as much as i can?
Nice Glad that Regex hack saved the day it’s a life-saver for those messy nested arrays.
Regarding the tokens, I definitely wouldn’t set up a recurring 1-hour timer. It’s a bit of a “capacity killer” in Bubble and honestly just more stuff that can break.
The much cleaner way is to do a “check before you use” approach. Basically, at the very start of your webhook workflow, just add a step that says: “If the current token is expired (or about to expire in 5 mins), run the refresh call now.”
This is way better because:
It’s efficient: You only refresh when you actually have data coming in.
It’s bulletproof: You aren’t relying on a background clock that might skip a beat; the refresh happens exactly when the webhook needs it.
Since your webhook is likely running as an API Workflow, just make sure you’re pulling that user’s token/expiry from your DB at the start of the flow to run the check. It’s much more “set it and forget it” than a scheduled hourly task
Now i’m having problem with duplicates created. I had the same one for google calendar webhook - couldn’t solve it as well.
So I receive multiple webhook responses I decode them, the i call Get History and then i process messages - Call Get Message and Create Emails in db with conditions to create it only if there is 0 messages in db with this message_id.
I’m NOT using schedule api workflow on a list. I create messages one by one, but duplicates are being created. I was wondering if there is any solutions for this…
Duplicates are the absolute worst, and it’s almost always because of a “race condition.” Basically, multiple webhooks hit your app at the exact same millisecond. They all check the DB, see that the message doesn’t exist yet, and they all hit the “Create” button before the first one can finish.
Since you’re already doing “only when count is 0,” here are the two most “human-proof” ways to kill the duplicates:
The “Search and Update” Trick: Instead of using the “Create a new thing” action, use “Create a new thing” but add a unique ID (like the Gmail Message ID) to a field. Then, right before that step, use an “Upsert” logic or a plugin like “Bulk Create/Update.” However, a simpler native way is to change your workflow to: 1. Search for the message.2. If not found, create it. To make this bulletproof, you need a “Lock.”
The “Pending” Lock (Best Fix): Create a new field on your User or a “Sync” object called Processing_IDs (list of texts).
When the webhook hits, the very first step is: Only proceed if Processing_IDs does not contain this Message ID.
Step 2: Add this Message ID to Processing_IDs.
Step 3: Do your “Get History” and “Create” logic.
Step 4: Remove the ID from Processing_IDs. This acts like a “gatekeeper” so only one workflow can handle that specific ID at a time.
Database Unique Constraints: If you can, make the Message ID a unique key. Bubble doesn’t have “unique constraints” in the traditional SQL sense, but you can use the API Connector to talk to your own Bubble Data API. The Data API is often stricter and faster at catching duplicates than the standard workflow actions.
I had the same headache with Calendar syncs the “Lock” method (adding the ID to a list while processing) is usually what finally stops the bleeding!
One extra thing I’d add: make the message_id write happen before any heavier processing, even if the rest fails later. If the webhook retries, that tiny “seen this ID” record is what keeps the duplicate from being created twice.
Hmm, but I have the message_id only after calling “Get History” which is pulling list of message_ids from the history_id of the webhook.
So it goes like this:
gmail_webhook is called
decoding data - request data’s message
retrieving history_id
calling get-history (using history_id from previous step)
retrieving list of messages’s ids
processing them and saving in db one by one
If i use:
"When the webhook hits, the very first step is: Only proceed if Processing_IDs does not contain this Message ID."
At this point i don’t have the message’s ids yet…
Hmm, but I have the message_id only after calling “Get History” which is pulling list of message_ids from the history_id of the webhook.
So it goes like this:
gmail_webhook is called
decoding data - request data’s message
retrieving history_id
calling get-history (using history_id from previous step)
retrieving list of messages’s ids
processing them and saving in db one by one
If i use:
"When the webhook hits, the very first step is: Only proceed if Processing_IDs does not contain this Message ID."
At this point i don’t have the message’s ids yet…
That’s a fair point—I forgot Gmail makes you jump through hoops just to find the actual IDs!
Since you don’t have the message_id until step 5, you just need to move that “Lock” logic down to Step 6 where the actual processing happens.
Even if you aren’t using “Schedule on a list,” you probably have a backend workflow that takes that ID and saves the email. Here’s how to kill the race condition right there:
The “Gatekeeper” List: On your User (or a ‘Sync’ object), create a list of texts field called Processed_IDs.
The “Claim” Step: The very first action in your processing workflow should be “Make changes to User” → Processed_IDs add this message_id.
Crucial: Add the condition: Only when User’s Processed_IDs doesn’t contain this message_id.
The Kill Switch: Step 2 should be “Terminate this workflow.”
Condition: Only when Result of Step 1 is empty. (This means the ID was already in the list, so this run is a duplicate and needs to stop immediately).
The Rest of the Flow: Now you can safely do your “Get Message” and “Create in DB” logic because only one workflow “won” the race to add that ID to the list.
The reason this works better than a “Search for count” is that Bubble’s “Add to list” action is way snappier at preventing duplicates when two webhooks hit at the exact same millisecond. By making the “check” and the “claim” happen in that very first step, you’re building a much tighter filter.
It’s an extra step, but it’s honestly the most reliable way to handle those hidden nested IDs!