Decoding an Instagram CDN URL — every query parameter, what it does, when it expires
Pull a Reel's video URL and you get a 400-character monster — scontent-cdg4-2.cdninstagram.com/..., followed by a dozen query parameters with cryptic names like _nc_ohc and efg. We decoded every parameter in a live URL today, measured the actual time-to-live (108 hours, not the ~1 hour you might expect), and explain why downloader tools that cache these URLs silently break user downloads. Also: the base64-encoded efg parameter contains more metadata than the filename does.
A real URL, caught in the wild
Here's a signed video URL our `/api/parse` endpoint returned on 2026-04-23, lightly redacted in the path to fit on one line:
`https://scontent-sjc6-1.cdninstagram.com/o1/v/t2/f2/m367/AQPhsUiY0kyEJd9K...DmE.mp4?_nc_cat=100&_nc_sid=9ca052&_nc_ht=scontent-sjc6-1.cdninstagram.com&_nc_ohc=D_vzzPpTe8gQ7kNvwHXhV6a&efg=eyJ2ZW5jb2RlX3RhZyI6...&ccb=17-1&_nc_gid=IkgfNkjEgLxGXhsILK1D3w&_nc_zt=28&_nc_ss=7a22e&oh=00_Af1gLg0mhAFF...HQ&oe=69EFEA01`
Nine query parameters, each doing something specific. The URL is ugly, but the shape is consistent across every Reel we've inspected in 2026 — same parameters, same order, same ordinal prefix conventions.
The host — geographic routing, not a stable identifier
`scontent-sjc6-1.cdninstagram.com`. The `sjc6-1` token maps to San Jose, California, data-center region 6, edge node 1. Paste the same shortcode and request the URL from a European IP, you'll get `scontent-cdg4-2.cdninstagram.com` (Paris / CDG, region 4, node 2) or similar. Edge routing is ASN-aware and aims to serve from the edge closest to the requesting IP.
For downloader tools running on a server somewhere, this means the host in the URL reflects where our proxy egressed from, not where the creator uploaded from, and not where the end user will download to. When our US-based request egresses through a US residential proxy, Instagram's URL generator picks a US edge. Run the same request through a European proxy and you'd get a different edge host with a completely different URL that still serves the same underlying asset.
Consequence: the `_nc_ht` query parameter (which mirrors the host value) is keyed to the egress point, not the asset. Copying a URL that was generated for one IP range and trying to use it from another IP range sometimes still works — Instagram is tolerant of cross-edge access — but some edges enforce stricter geo-binding and will reject the signature.
The _nc_ prefix family — network / cache / content hints
Six of the nine parameters start with `_nc_`. Meta hasn't published documentation for what the abbreviation stands for (the prevailing guess is "network content" or "native content"), but empirically we know what each does.
`_nc_cat=100` — the cache bucket or category identifier. Values cluster in a narrow range (we've seen 100, 105, 108 in the same response), suggesting partition buckets for CDN storage tiers.
`_nc_sid=9ca052` — a session-ID-like token scoped to the asset and request context. Repeat calls to parse the same Reel return the same `_nc_sid`, so it's not per-request-random. More likely a deterministic hash tied to the asset + encoding variant.
`_nc_ht=scontent-sjc6-1.cdninstagram.com` — the host value, mirrored. The CDN uses this for signature validation (the HMAC covers the host), and having it in the query string lets Instagram detect tampering where someone copies an asset token to a different edge host.
`_nc_ohc=D_vzzPpTe8gQ7kNvwHXhV6a` — origin hit counter / cache validator. Our best guess is this is a monotonically-advancing token the origin server increments on cache invalidations; the CDN edge checks it against its cached copy to decide whether to re-fetch. Changes when the underlying DASH segments get re-packaged.
`_nc_gid=IkgfNkjEgLxGXhsILK1D3w` — request / group ID. Uniform across a single `/api/parse` response (video, audio, and thumbnail URLs all share the same `_nc_gid`). Likely the origin's internal trace ID for joining related asset requests in their logs.
`_nc_zt=28` — zone or tier identifier. We see this as a small integer, 24 to 32 range. Possibly indicates the content tier (music Reels vs standard, branded vs organic, etc) or the pricing tier for egress purposes.
`_nc_ss=7a22e` — signing salt or scheme version. Five-hex-digit values, moderately stable across similar assets. Part of the HMAC input.
ccb — the codec/container bump
`ccb=17-1`. Two numbers separated by a dash. The first is currently 17 on every Reel we've seen; the second is usually 1, occasionally 0 or 2.
Our reading: `ccb` stands for "codec/container bump" or version number. When Meta rolls a new transcoding profile, they bump the first digit. The second digit tracks minor variant changes within a profile generation. Historical URL captures we've collected (going back to 2023) show `ccb=16-0` from that era, suggesting the first number advances roughly every year or two. For the blog's purposes: it identifies which encoding generation the asset belongs to, and the CDN uses it to route to the right decoder-compatibility layer.
efg — the metadata record hiding in plain sight
`efg=eyJ2ZW5jb2RlX3RhZyI6...` is base64-encoded JSON. Decode it and you get a structured record that carries significantly more information than the URL's path and filename combined.
Today's URL decoded to: `{"vencode_tag":"ig-xpvds.clips.c2-C3.dash_baseline_1_v1","video_id":null,"oil_urlgen_app_id":936619743392459,"client_name":"ig","xpv_asset_id":1170775294179927,"asset_age_days":386,"vi_usecase_id":10099,"duration_s":60,"bitrate":1356360,"urlgen_source":"www"}`.
Worth unpacking piece by piece. `vencode_tag=ig-xpvds.clips.c2-C3.dash_baseline_1_v1` — Instagram's internal tag for which encoding profile produced this asset variant. `clips` means Reels; `dash_baseline_1_v1` means DASH-packaged H.264 baseline-ish, profile version 1. We've seen `c2-C3` and `c2-C4` depending on the edge; these are probably cluster identifiers.
`oil_urlgen_app_id=936619743392459` — the Facebook/Meta internal application ID that generated this signed URL. This is Meta's "Video for Facebook" service's app ID, visible in third-party tooling. Same value appears on every Reel URL; it's the URL generator's self-identification.
`client_name=ig` — generated for Instagram (vs Facebook, Threads, WhatsApp). `urlgen_source=www` — generated for the web surface (vs mobile apps, which emit slightly different URL shapes).
`xpv_asset_id=1170775294179927` — the 64-bit internal asset ID, distinct from the shortcode (which wraps a post-level ID, not an asset-level one). One Reel post can have multiple xpv_asset_ids: one per resolution variant, plus one for the audio track. You can use this to join video and audio segments back to the same post.
`asset_age_days=386` — how old the asset is, in days since the original upload-transcode. The Reel we tested today was transcoded 386 days ago; the URL generator reports this alongside the signed URL so clients can decide how "fresh" the asset is.
`duration_s=60` and `bitrate=1356360` — clip duration in seconds, video bitrate in bits per second. Redundant with what ffprobe extracts from the file itself, but included here so clients can make bandwidth / cache decisions before fetching.
`vi_usecase_id=10099` — Meta's internal taxonomy of why the URL was generated. Downloader-adjacent, probably keyed to "standard public Reel playback" in their system.
The point of all this: Instagram isn't hiding this metadata from tooling that can decode it. The `efg` is client-facing and everything in it is informational. If you're building an archive tool that wants to record asset-level IDs and transcoding ages, the `efg` is where you pull that data.
oh — the HMAC signature that makes the whole URL work
`oh=00_Af1gLg0mhAFFQVWHW-iwfWtDw0ewSIXHtPzd4PsT7slpHQ`. This is the HMAC signature covering the rest of the URL, the expiry, and some aspects of the requesting client identity. Without it, the CDN returns 403 on any request to the signed path.
The `00_` prefix is a version marker — Meta has used different signature prefixes over the years (older URLs had different leading bytes, and the current production signatures have been on `00_` since roughly 2022). The 44 base64 characters after the underscore are the actual HMAC output.
The signature input almost certainly includes the asset path, the expiry timestamp (the `oe` parameter below), the `_nc_ht` host value, and the `_nc_ss` salt. We haven't reverse-engineered the exact input string because there's no practical reason to — HMAC-SHA256 with an unknown key means forging the signature requires either Meta's key (which you can't have) or a key rotation gap (which you can't predict).
Any tool claiming to forge these signatures is selling vaporware. The HMAC is standard, the key is server-side, and Meta rotates periodically. Valid URLs come from Meta; no other source.
oe — the expiry, in hex Unix seconds
`oe=69EFEA01`. Parse this as hexadecimal and you get the decimal 1777330689. That's a Unix timestamp — seconds since 1970-01-01 UTC — resolving to `2026-04-27T22:58:09Z`.
The URL we pulled was generated at roughly 18:00 UTC on 2026-04-23. Delta to expiry: about 108 hours, or 4.5 days. That's significantly longer than we initially assumed from casual reading of signed-URL literature (most signed-URL systems default to 1-6 hour windows).
We measured repeatedly across the afternoon and every parse call for the same Reel returned the same `oe` value. This suggests Meta is generating the URL with a fixed target expiry (a specific moment in the future, computed once), not a rolling window that advances with each request. The signature depends on the expiry, so any change to `oe` forces a new `oh`, and both hold across repeat calls until the actual expiry approaches.
Practical consequence for downloaders: the signed URLs are usable for several days, not minutes. That's much more forgiving than we initially credited in our earlier DASH-streaming write-up (which claimed ~1 hour based on outdated assumptions). The caching concern still holds, but for a different reason — even with 108-hour windows, caching parsed URLs longer than that breaks, and you can't distinguish an expired URL from a valid one until you request it.
What happens when oe expires
We took a URL with `oe` about 60 seconds away from expiry and requested it a minute after the timestamp had passed. The CDN returned an HTTP 403 with a body that ffprobe couldn't parse (roughly 300 bytes of XML error text). No partial content, no useful error headers. The URL is simply gone from the CDN's perspective once its expiry clock hits zero.
For downloader tools, this means a URL generated right now is valid for ~4.5 days. A URL stored in your database a week ago will return 403. There's no way to extend an expired URL; you have to generate a fresh one by calling `/api/parse` again.
This is also why our `/api/parse` responses aren't cached by our edge proxy beyond a short window. Every user paste generates a fresh call to Instagram, which in turn generates a fresh URL (or returns the same one if it's still within the expiry window). We pay for the extra proxy egress but users never see a 403 from trying to use a stale URL.
How this compares to other CDN signature schemes
Amazon CloudFront signed URLs use a similar pattern: base URL, query parameters including `Expires` (Unix seconds), `Signature`, `Key-Pair-Id`. Meta's scheme is less self-documenting but structurally equivalent — you have the path, the expiry, the HMAC, and some context that binds the signature to a specific request pattern.
Cloudflare Signed URLs (used on Cloudflare Stream and similar) use `token` and `exp` query params. Same underlying primitive.
The industry consensus across all these schemes: URLs are opaque tokens, the HMAC binds the URL to the intended client context, expiry is absolute not relative, and no third party can forge a valid URL. Meta's implementation is in line with best practice, maybe slightly more opaque on the parameter naming side because Meta hasn't published docs for it the way AWS and Cloudflare have.
What this means for tools downstream
**Don't cache parsed URLs beyond their natural lifetime.** Our backend doesn't cache `/api/parse` results at all; every user paste hits Instagram fresh. Slower on our side, but users never hit a stale URL and never see a mystifying 403.
**Don't bookmark Instagram CDN URLs directly.** If a user saves a Reel's direct CDN URL as a bookmark, it's dead within a few days. Bookmark the Instagram post URL (`instagram.com/reel/SHORTCODE/`) which is permanent.
**Don't build features that assume URLs are stable.** A download button that embeds the CDN URL in a shareable link will work for the sharer but may fail for the recipient if the recipient clicks after expiry. Our Share Strip uses the Instagram post URL as the canonical sharing target.
**Do use the efg parameter for metadata.** If you're archiving downloaded Reels and want to track Instagram's internal asset IDs for future reference, base64-decode the `efg` at parse time and store the JSON record alongside your download. The URL itself will expire; the `efg` data won't.
The honest summary
Instagram's signed CDN URLs are 9 parameters deep, HMAC-protected, and expire in ~108 hours from generation. Every parameter serves a specific purpose — cache routing, signature validation, asset identification, expiry enforcement.
The `efg` parameter carries more metadata than the filename, including asset age, bitrate, encoding profile, and Meta's internal ID. Most tools ignore it. It's the best source of provenance data we've found for downloaded Reels.
No third party can forge these URLs. Signatures come from Meta, keys stay server-side. Any tool advertising "direct CDN access without parsing" is either replaying a user's URL until it expires or simply lying.
Our sister posts on the surrounding pipeline: [inside Instagram's DASH manifest](https://instayolo.com/blog/instagram-dash-streaming-explained), [the MP4 metadata fields Instagram strips](https://instayolo.com/blog/instagram-mp4-metadata-stripping), and [how residential proxies handle IG's edge rules](https://instayolo.com/blog/how-residential-proxies-bypass-instagram-cdn).
FAQ
- How long does an Instagram CDN URL stay valid?
- About 108 hours (4.5 days) from generation, measured on 2026-04-23 against live production URLs. The `oe` query parameter is a hex Unix timestamp you can decode directly. Older blog posts (including our own earlier DASH post) sometimes claim ~1 hour — that number was wrong or reflects an older Meta policy. Current production TTL is several days.
- Can I just save the CDN URL and come back to it later?
- For a few days, yes. After the `oe` expiry, the URL returns 403 Forbidden and there's no way to refresh it without generating a new signed URL via the parse API. For permanent storage, download the file itself — the signed URL is ephemeral, the MP4 you save to disk is forever.
- Is the HMAC signature something I can reverse-engineer?
- No. It's HMAC with a server-side key you don't have and Meta rotates periodically. Reverse-engineering ECMA-standard HMAC without the key is computationally infeasible; anyone claiming to do it is running some other scheme underneath. Downloader tools that work do so by calling Meta's own URL generator via the front-end GraphQL endpoint, not by forging signatures.
- What is the efg parameter actually for?
- It's base64-encoded JSON containing Instagram's internal metadata about the asset: encoding profile, asset age in days, bitrate, the original app ID that generated the URL, and an internal asset ID. Base64-decode it and parse as JSON to read the fields. Tools that archive Reels for research can use this as a source of provenance data the downloaded MP4 itself doesn't carry.
- Why does my CDN URL have a different host than my friend's?
- Edge routing is geographic. Meta picks the edge closest to the requesting IP. Your request from one geography gets one edge host; your friend's request from a different region gets another. Same underlying asset, different CDN node serving it. The content is identical even though the URLs differ.