Workflow AutomationOperationsn8nZapier

Attachment Links Dead After a Sync: The URL Was a Lease

Attachment links die after a sync because the URL an API hands you is a lease, not an address: Airtable download URLs last about 2 hours, Notion-hosted files and OneDrive download URLs last 1 hour, Dropbox temporary links last 4 hours, S3 presigned URLs last at most 7 days and die early with the credential that signed them, and Slack and HubSpot private files never open without the caller's own token. The automation reads the link within seconds so the run succeeds; the human who clicks the stored link days later gets the failure. The fix is to copy the bytes into storage you own while the lease is fresh, store the object key plus the source system and source file ID, and mint a fresh link at read time.

Alexey YushkinFounder, GENERAL INFORMATICS3 min read

Attachment links die after a sync because the URL an API hands your automation is a lease, not an address. Airtable download URLs last about 2 hours, Notion-hosted files and OneDrive download URLs last 1 hour, Dropbox temporary links last 4 hours, and an S3 presigned URL lasts at most 7 days and dies early with the credential that signed it. Slack and HubSpot private files never open without the caller's own token. The automation fetches the file seconds after receiving the link, so every run passes. The salesperson who clicks the same link in the CRM a week later gets a 403, a 410, or a sign-in page. The fix is to copy the bytes into storage you own while the lease is fresh, store the object key and the source file ID, and mint a fresh link at read time.

This bug is invisible to testing because the automation and the human read the link on different clocks.

Zapier, Make, and n8n all treat a file URL the same way: they receive it from the trigger, and within seconds a later step downloads it, uploads it, or writes it into a record. That download happens well inside any platform's validity window, so the step succeeds, the file arrives where it was sent, and the run log is green.

The URL itself is what gets written into the CRM note, the ticket, the spreadsheet cell, the Slack message. Nothing reads it again until a person clicks it. By then the window is closed. Airtable commits to keeping a download URL active for "at least 2 hours" and states that URLs obtained through the API or a third-party integration are "only active for a short period of time (~2 hours)." Notion's API reference says each fetch of a Notion-hosted file "includes a temporary public url valid for 1 hour." Microsoft Graph says the OneDrive and SharePoint download URL "is a short-lived URL and can't be cached," invalidated after 1 hour. Dropbox says a temporary link "will expire in four hours and afterwards you will get 410 Gone."

Every one of those is a documented, intended behavior. None of them is a bug in your automation. The bug is that a value with a lifetime of hours was written into a record with a lifetime of years.

Zapier's help text on sending files describes the same failure from the other side: "If your Zap fails with a 403 error or the file arrives empty, check that your URL is a direct download link and does not require authentication." Caught early there only because Zapier happened to be the reader. When the reader is a person next month, nobody catches it.

How long a file URL lives on each platform

We checked each row against the vendor's current documentation in September 2026.

SourceWhat the API hands youLives forOpens without a credential?What to store instead
Airtable attachmentA download URL on the attachment objectAt least 2 hours, about 2 hours in practiceYes, until it expiresRecord ID plus attachment ID, and your own copy of the bytes
Notion (file uploaded in Notion)url plus an expiry_time on the file object1 hourYes, until it expiresPage or block ID; re-fetch for a new URL
Notion (external file)A URL you suppliedNever expires on Notion's sideWhatever the host you pointed at allowsThe same URL, but only if that host is one you own
OneDrive and SharePoint (Graph)@microsoft.graph.downloadUrl1 hour, explicitly "can't be cached"Yes, "authentication isn't required with this URL"Drive ID plus item ID
Dropbox/files/get_temporary_link4 hours, then 410 GoneYes, until it expiresThe file path or id: and a re-fetch
Amazon S3 presigned URLA signed GET URLWhat you set, capped at 12 hours from the console and 7 days from CLI or SDK, and never longer than the signing credential livesYes, until it expiresBucket plus object key; sign at read time
Google DrivewebContentLink and webViewLinkStableOnly for accounts the file is shared with; thumbnailLink is separately short-lived, "on the order of hours"File ID, and confirm the reader's permission
Slack fileurl_private, url_private_downloadStableNo. Both "require an authorization header" with a token holding files:readFile ID; download with your token or call files.sharedPublicURL
HubSpot file set to PrivateThe file URLStableNo. Private files need a signed URL from GET /files/v3/files/{fileId}/signed-url, which carries an expiresAt; the UI's temporary public share link "will expire after 24 hours"File ID; request a signed URL at read time

Two things in that table set the plan. First, the rows split into two families and both fail. The expiring family (Airtable, Notion, OneDrive, Dropbox, S3) hands you a link that works for anyone and then stops working for everyone. The credential family (Slack, HubSpot private, Google Drive) hands you a link that works forever for the automation and never for the wrong reader. Second, nothing in either family is fixed by a longer expiry. The longest lease on the table is S3 at 7 days, and AWS notes that a URL signed with temporary credentials "expires when the credential expires," which for an assumed role is 1 hour by default. A signed URL minted inside a Lambda or an n8n worker running under a role can die 167 hours before the expiry you wrote into it.

The three shapes the failure takes

The symptom the operator sees depends on which family the source belongs to and where the link was stored.

The link expired. A crew photo lands in Airtable, an automation posts the download URL into the job's HubSpot note, and the estimator opens it three days later. Airtable serves an error page. This is the common one, and it is the one people try to fix by "syncing again," which mints a new two-hour lease and resets the same timer.

The link needs a credential the reader does not have. A customer attaches a PDF in Slack, the automation writes url_private into the ticket, and the support agent clicks it from the helpdesk. Slack does not return the file without the bearer header. The automation, which had the header, downloaded the same file fine in the run log. This one produces the most confused threads because the file is demonstrably reachable, just not by the person who needs it.

The link outlives the permission. Google Drive links and OneDrive download URLs do not expire on a clock the way Airtable's do, so they get trusted. Then the file gets moved to a folder the rep cannot see, or shared access is pulled during an offboarding, and the link in the CRM points at a file the reader can no longer open. Microsoft documents the mirror image: "Removing file permissions for a user might not immediately invalidate the URL." A stable link is not the same thing as a stable grant.

There is a fourth shape that combines the first two. Notion accepts an "external" file whose URL never expires on Notion's side. Point that external file at an Airtable download URL, which an automation will happily do, and Notion stores a permanent reference to a link that dies in 2 hours. The Notion page shows a broken image forever, and nothing in Notion's API will tell you why.

Store the file, not the lease

The rule we build to: an automation may consume a file URL, but it may not store one, unless the thing that renders it mints a fresh URL on open.

Consuming means downloading the bytes while the lease is fresh and putting them somewhere you own, an S3 bucket, a Google Cloud Storage bucket, your own Drive folder under a service account. That is the same move described in pass links, not bytes, from the other direction. That article is about keeping the bytes out of the workflow's memory. This one is about keeping the lease out of the record. Both land on the same design: exactly one copy of the file in storage you control, referenced everywhere else by key.

What goes on the record is four fields, not one.

  1. The object key or file ID in your storage. This is the durable reference.
  2. The source system name, as a value from a fixed list, so a reader knows where the original lives.
  3. The source file ID: the Airtable attachment ID, the Slack file ID, the Drive file ID, the Graph item ID. This is what lets a later job re-fetch the original if your copy is missing or the source was edited.
  4. A content hash of the bytes you stored, so a re-sync can tell "same file" from "new version" without downloading anything.

Log the object key on the run record, the way what to log in every automation describes. Never log the URL. A presigned URL or an expiring Airtable link in a log is a credential with a timer on it, and the log will outlive the timer.

When the destination has a native file field, use it, because those fields consume the lease for you. Airtable's attachment field downloads the file when you hand it a URL, which is why the sibling article can cite a 5 GB ceiling for attachments added by link. HubSpot's file import from a URL does the same. The download happens at write time, inside the window, and the destination holds its own bytes from then on. The trap is a plain text or URL field, which stores the string and nothing else.

The one place a URL belongs in a record is where the app that shows the record generates it on open. A customer portal, an internal dashboard, a mobile app for a crew: each of those can hold the object key and ask storage for a 15-minute signed URL when a person clicks. That is the pattern behind crew photos in Field to Flow: the photo is an object, the job holds the key, and the office view mints the link when someone opens the job. No stored link, so no stored expiry. It also sidesteps the S3 credential trap, since a URL signed seconds before it is used never outlives the role session that signed it.

The ten-minute test that catches it

Pick one record that an automation populated with a file link in the last month. Then do three things.

Open the link in a private browser window, signed out of everything. If you get a sign-in page or a 403, you are storing a credential-family link and every reader without the automation's token is locked out.

If it opened, wait 3 hours and open it again from the same private window. Airtable, Notion, OneDrive, and short S3 leases will all have expired by then. Dropbox needs 4. If the second open fails, you are storing an expiring-family link.

If it still opens, find the oldest record with a stored link, ideally over a week old, and click it as the least-privileged user who is supposed to be able to see that file. This catches the permission-drift case, and it is the one that only shows up on old records.

Then look at the automation's run log for that record. If the log line contains the URL and not an object key, fix the logging at the same time you fix the field. The reconnect loop is what happens to OAuth tokens that were treated as permanent. Stored file links are the same mistake with a shorter fuse.

What to do next

List every automation that writes a file link into a record and sort them by the table above. The expiring rows are the emergency, because every link they have ever written is already dead. Convert those first: add a storage upload step ahead of the write, replace the URL field with the four fields above, and backfill old records from the source file IDs if you kept them. If you did not, the originals are still in the source system and a one-time job can re-attach them by record ID.

If the files are jobsite photos, signed forms, or inspection documents moving between a field app and an office system, that is the pipeline shape we build under operational intelligence systems, and the storage-key pattern is the first thing we put in. If you want a second set of eyes on which of your file links are leases, tell us which platforms are on each end and we will map them against the table.

Frequently Asked Questions

SOURCES & CITATIONS

  1. Airtable attachment URL behavior Airtable Supporthttps://support.airtable.com/docs/airtable-attachment-url-behavior
  2. File object, Notion API reference Notionhttps://developers.notion.com/reference/file-object
  3. file object, Slack API reference Slackhttps://docs.slack.dev/reference/objects/file-object/
  4. driveItem resource type, Microsoft Graph v1.0 Microsoft Learnhttps://learn.microsoft.com/en-us/graph/api/resources/driveitem?view=graph-rest-1.0
  5. Download and upload objects with presigned URLs Amazon Web Serviceshttps://docs.aws.amazon.com/AmazonS3/latest/userguide/using-presigned-url.html
  6. Get temporary link, Dropbox API reference Dropboxhttps://docs.dropboxapi.com/dropbox-api/api-reference/user-endpoints/files/get-temporary-link
  7. Organize, edit, and delete files HubSpot Knowledge Basehttps://knowledge.hubspot.com/files/organize-edit-and-delete-files

About Alexey Yushkin

Alexey is the founder of GENERAL INFORMATICS LLC. He designs and ships AI and automation systems for businesses and operators across the US.

Connect on LinkedIn

Related reading

Want this kind of system in your business?

We build practical AI and automation systems for operators. Send us your current workflow and we will show you what to automate first.