Find Your Best Time to Post From Link Click Data
Part of the Link tracking handbook
The big best-time studies measure other people's engagement and disagree with each other. Your own links' click times show the best time to post for your audience.
To find the best time to post for your audience, look at when your own links get clicked, in your audience's local time, and compare that with when you posted. The published "best time to post" studies measure engagement across millions of other accounts, and they don't agree with each other.
This guide shows what the big studies say, why your click timestamps are a better signal for posts that carry a link, and how to turn them into a weekday-by-hour grid with a short script. It is part of the link tracking handbook.
How to find the best time to post for your audience
- Put a tracked short link in every post for a few weeks, one link per post or placement.
- Post at a range of times, not always the same slot, and note each posting time.
- Pull the click timestamps through the API and drop the automated ones (previews, scanners).
- Convert each click to the clicker's local time and count clicks by weekday and hour.
- Pick the strongest window, then test it against your usual slot before switching.
What the big studies say, and why they disagree
| Study | Published | Data | Metric and time zone | Headline answer |
|---|---|---|---|---|
| Sprout Social | 31 Mar 2026 | Nearly 2 billion engagements, about 307,000 profiles, 27 Nov 2025 to 27 Feb 2026 | Engagements (reactions, comments, shares, link clicks and more); user's local time | Tuesday and Wednesday, late morning to early evening; Sunday weakest |
| Hootsuite | 19 Nov 2025 | Over 1 million posts, normalised across 118 countries | Engagement; time-zone agnostic | Wednesday, 8 a.m. |
| Buffer | 25 Mar 2026 | Over 52 million posts | Median engagement (reach for Instagram, views for TikTok); local time | Instagram Thursday 9 a.m.; LinkedIn Wednesday 4 p.m.; TikTok Sunday 9 a.m. |
| Later (Instagram) | 21 Apr 2026 | Over 6 million feed posts, Jan to Oct 2024 | Engagement rate; audience's local time | 5 a.m. |
They disagree for ordinary reasons. Each studies its own customers' accounts. Each picks a different measure (total engagements, a rate, a median) and a different period. Some convert to local time, one normalises across countries. And the answer that tops a table is often a narrow win in noisy data.
None of them tells you when people click links. Sprout counts link clicks inside "engagements", alongside likes and comments, and the others measure engagement or views. If your post exists to send people somewhere, that is the number you want, and only your own data has it.
Why clicks are a better signal for link posts
A like takes a second and happens in the feed. A click means someone stopped scrolling and left the app, which is the outcome a link post is for. Timing the clicks times the thing you care about.
Two traps to know before you read the data:
- Clicks follow posts. If you always post at 9 a.m., most clicks will land after 9 a.m., and the data will "prove" 9 a.m. is your best time. Post at varied times for a few weeks first, and compare clicks per post, not clicks per hour of the day.
- Previews arrive at posting time. Chat apps and some social platforms fetch a link the moment it is posted, and SendURL counts those fetches. Filter them out, or the posting hour wins every time. Bot clicks and link previews explains the signals; the script below applies the simple ones.
Get your click times
Every click record has a clickedAt timestamp in UTC and a two-letter country code (click object). The script further down pages through all clicks of the links you name, 10 per request, and drops clicks with a bot user agent or no browser in the parsed record. You'll need an API key from the console in SENDURL_API_KEY, and the id of each link, which is in its console address (console.sendurl.to/links/<id>).
Free lists 7 days of analytics history and Pro 30; the link page and the API cover every click, so the script sees older clicks too.
Convert to your audience's local time
A click at 13:00 UTC is lunchtime in London, late evening in Tokyo and early morning in New York. Reading UTC hours mixes all three. SendURL records the country but no city or time zone, so the script maps each country to one representative time zone:
- Single-zone countries convert exactly: France (
Europe/Paris), the UK, Germany, Japan, the UAE, Singapore and most of Europe. - Multi-zone countries get their most populous zone and are an approximation: the US (
America/New_York), Canada, Australia, Brazil, Mexico, Russia, Indonesia. If most of your audience is in one of these, read the result as plus or minus a few hours, or split it out and look at it alone.
Clicks with no country, or from a country missing in the map, are left out and counted.
Build the heatmap
The script needs requests (pip install requests).
import csv, os, re, time
from collections import Counter
from datetime import datetime
from zoneinfo import ZoneInfo
import requests
API = "https://api.sendurl.to/api/v1"
HEADERS = {"Authorization": f"Bearer {os.environ['SENDURL_API_KEY']}"}
BOT_UA = re.compile(r"Slackbot|facebookexternalhit|Twitterbot|bot/|crawler|spider|curl/|python-requests", re.I)
# One zone per country. US, CA, AU, BR, MX, RU and ID span several zones, so their hours are approximate.
ZONES = {
"GB": "Europe/London", "IE": "Europe/Dublin", "DE": "Europe/Berlin", "FR": "Europe/Paris",
"NL": "Europe/Amsterdam", "IT": "Europe/Rome", "PL": "Europe/Warsaw",
"AE": "Asia/Dubai", "SG": "Asia/Singapore", "JP": "Asia/Tokyo", "PH": "Asia/Manila",
"PK": "Asia/Karachi", "BD": "Asia/Dhaka", "NG": "Africa/Lagos", "ZA": "Africa/Johannesburg",
"US": "America/New_York", "CA": "America/Toronto", "AU": "Australia/Sydney", "BR": "America/Sao_Paulo",
"MX": "America/Mexico_City", "RU": "Europe/Moscow", "ID": "Asia/Jakarta", "ES": "Europe/Madrid",
}
DAYS = ["1-Mon", "2-Tue", "3-Wed", "4-Thu", "5-Fri", "6-Sat", "7-Sun"] # numbered, so a sheet sorts them in order
def clicks(link_id):
params = {"limit": 10}
while True:
r = requests.get(f"{API}/links/{link_id}/clicks", headers=HEADERS, params=params, timeout=10)
r.raise_for_status()
page = r.json()
yield from page["clicks"]
if not page["pagination"]["hasMore"]:
return
params["cursor"] = page["pagination"]["nextCursor"]
time.sleep(0.25) # stay under the API's rate limit
grid, skipped = Counter(), Counter()
with open("click_times.csv", "w", newline="") as f:
out = csv.writer(f)
out.writerow(["link_id", "clicked_at_utc", "country", "local_weekday", "local_hour"])
for link_id in os.environ["LINK_IDS"].split(","):
for c in clicks(link_id.strip()):
ua = c["userAgent"] or ""
if not ua or not c["browser"] or BOT_UA.search(ua):
skipped["automated"] += 1
continue
zone = ZONES.get(c["country"] or "")
if zone is None:
skipped["no time zone"] += 1
continue
utc = datetime.fromisoformat(c["clickedAt"].replace("Z", "+00:00"))
local = utc.astimezone(ZoneInfo(zone))
grid[(local.weekday(), local.hour)] += 1
out.writerow([link_id, c["clickedAt"], c["country"], DAYS[local.weekday()], local.hour])
print(" " + "".join(f"{h:>4}" for h in range(24)))
for d, name in enumerate(DAYS):
print(f"{name[2:]:<5}" + "".join(f"{grid[(d, h)]:>4}" for h in range(24)))
print("skipped:", dict(skipped))Run it with your link ids, comma-separated:
LINK_IDS="5d8f2a1c-7b3e-4c9d-a0e1-6f2b8c4d1e7a,0e9d8c7b-6a5f-4e3d-9c2b-1a0f9e8d7c6b" python3 click_heatmap.pyIt prints a grid of clicks per local weekday and hour, and writes every kept click to click_times.csv. zoneinfo ships with Python 3.9 and later; on Windows, also run pip install tzdata.
In Google Sheets instead
Import click_times.csv and build a pivot table: Rows local_weekday, Columns local_hour, Values clicked_at_utc summarised by COUNTA. The weekdays are written 1-Mon to 7-Sun so the pivot sorts them Monday first, not alphabetically. Add conditional formatting (a colour scale) on the values and you have the heatmap. To work from raw UTC times in a sheet, turn the API's timestamp into a date-time with =DATEVALUE(LEFT(B2,10))+TIMEVALUE(MID(B2,12,8)), then add your audience's UTC offset in hours divided by 24 (this ignores daylight saving, so check twice a year).
Read the grid
- This grid shows when clicks happen, not which posting slot works best. To compare slots, divide each post's clicks by the number of posts you made in that slot, using the
link_idcolumn and your posting log. A slot you posted in eight times will otherwise beat one you used twice. - Look at the delay as well as the hour. Clicks that peak two hours after posting tell you to post two hours before the peak.
- Read each channel on its own. A newsletter, an Instagram bio and a LinkedIn post have different rhythms; one link per placement keeps them apart. The social media link tracking guide and the Instagram link tracking guide cover the placements. With a single TikTok bio link, the same timestamps also suggest which video sent the clicks: see how to track TikTok bio link clicks.
- Ignore thin cells. With fewer than a few hundred clicks, read the hour of day on its own and leave the weekday split for later.
Test it: post at two times, compare
The grid gives you a hypothesis, not a verdict. Pick the best window and your usual slot, alternate them over the next few weeks with comparable posts, and give each post its own short link. Then compare clicks per post with a two-proportion test, using the platform's impressions as the denominator; A/B test with short links has the method and the code.
Checklist
- A tracked short link in every post you want to time, one per post or placement
- Posting times written down, and varied for a few weeks before you read anything
- Automated clicks removed, especially those right at posting time
- Clicks converted to local time; multi-zone countries flagged as approximate
- Clicks per post compared across windows, not raw clicks per hour
- The winning window tested against your usual slot before you switch
- The analysis repeated every few months; audiences drift
For pulling larger click exports, see the URL shortener API guide for Python and JavaScript.
Frequently asked questions
How do I find the best time to post for my audience?
Use your own data. Put a tracked short link in each post, pull the click times, convert them to your audience's local time and count clicks by weekday and hour. Then test the busiest window against your usual one.
Why do best-time-to-post studies disagree?
They measure different things on different accounts: total engagements, engagement rate or median engagement, over different months, with different time-zone handling. None of the big studies reports link clicks on their own.
How many clicks do I need before the pattern means anything?
More than a week's worth. A few hundred clicks spread over several weeks and several posting times is a reasonable minimum before reading a weekday-by-hour grid; with fewer, look at the hour of day only.
Start tracking links free.
Create your first short link with click analytics in seconds. No credit card, no sign-up to start.
Shorten a link