How to make a link preview animate in iMessage, Slack, WhatsApp, Discord and Telegram
Checked November 2026. “Verified by us” means we saw it animate in that app ourselves; everything else comes from the linked docs or observations.
TL;DR
No single tag animates everywhere. Put the animated GIF itself in og:image (Slack shows it moving) and a downloadable H.264 MP4 of the same loop in og:video (iMessage downloads and plays it). Serve both as plain files at stable HTTPS URLs, with the tags in the first HTML response. WhatsApp's spec only describes a thumbnail for links, so expect it to stay still (we haven't tested it); when motion matters there, share the GIF file instead of the link. Discord and Telegram can show animated media, but which tags they use for an ordinary site isn't documented: test them.
Per app
| App | Crawler user agent | What animates | Limits | Verified by us | Source |
|---|---|---|---|---|---|
| iMessage | No documented Messages user agent. An old observation shows both facebookexternalhit/1.1 and Twitterbot/1.0 (plus Facebot). Applebot is a different crawler. | Documented: a direct, downloadable MP4 in og:video (or twitter:player:stream) is downloaded and played automatically. An iframe player doesn't play inline; HLS needs a tap. A GIF in og:image alone is less clearly specified. | Apple's guidelines: main HTML up to 1 MB; images, icons and video combined up to 10 MB; images at least 900 px wide are recommended, under 150 px may show as an icon. | Yes. macOS Messages, GIF og:image + MP4 og:video: the card animated and was labelled “Video”. | Apple TN3156 UA observation |
| Slack | Slackbot-LinkExpanding 1.0 (+https://api.slack.com/robots); images are fetched by Slack-ImgProxy. | A GIF in og:image. og:video alone doesn't play: Slack only embeds video from hosts on its allowlist. | No published size limit for GIF unfurls found. Unfurls are cached for about 30 minutes, and the same link isn't unfurled again in a conversation within an hour. | Yes. Slack web, GIF og:image: two captures a second apart showed different frames. | Slack robots Slack help |
WhatsApp/2.x.x.x A, I or N (Android, iOS, web), e.g. WhatsApp/2.22.20.72 A. | Expected: a static thumbnail (our reading of the spec, untested). The spec reads og:title, og:description, og:url and og:image only; no documented tag makes the card animate. Send the GIF as a file instead (below). | og:image under 600 KB, at least 300 px wide, aspect ratio at most 4:1; the tags must be in the first 300 KB of the HTML. | No. Not tested yet. | WhatsApp link previews | |
| Discord | Mozilla/5.0 (compatible; Discordbot/2.0; +https://discordapp.com) (observed, not documented). | Likely a GIF in og:image with twitter:card = summary_large_image. Untested: an MP4 og:video may turn the embed into a click-to-play video, so compare with and without it. | No public limit for embedded GIFs found (upload limits are a different thing). Users' GIF autoplay, reduced-motion and media-preview settings affect what they see. | No. Not tested yet. | Discord embed object UA observation |
| Telegram | TelegramBot (like TwitterBot) (observed). Match it before any generic Twitterbot rule. | Telegram's link-preview model has a GIF type, so animated previews exist. Which tags trigger it for an ordinary site isn't documented: test a GIF og:image, an MP4 og:video and a raw .gif link. | No public limit for link previews found. Auto-download and autoplay settings can stop the motion. | No. Not tested yet. | Telegram webPage UA observation |
Copy-paste <head>
Use absolute HTTPS URLs and the real width and height of each file. Leave out the og:video and twitter:player:stream lines when you have no MP4.
<meta property="og:type" content="video.other">
<meta property="og:url" content="https://example.com/m/abc123">
<meta property="og:title" content="Build passed first try... suspicious">
<meta property="og:description" content="A captioned GIF">
<!-- Slack, Discord: the animated GIF itself, not a screenshot -->
<meta property="og:image" content="https://example.com/m/abc123.gif">
<meta property="og:image:secure_url" content="https://example.com/m/abc123.gif">
<meta property="og:image:type" content="image/gif">
<meta property="og:image:width" content="480">
<meta property="og:image:height" content="360">
<meta property="og:image:alt" content="Build passed first try... suspicious">
<!-- iMessage: a downloadable H.264 MP4 of the same loop -->
<meta property="og:video" content="https://example.com/m/abc123.mp4">
<meta property="og:video:secure_url" content="https://example.com/m/abc123.mp4">
<meta property="og:video:type" content="video/mp4">
<meta property="og:video:width" content="480">
<meta property="og:video:height" content="360">
<meta name="twitter:card" content="summary_large_image">
<meta name="twitter:image" content="https://example.com/m/abc123.gif">
<meta name="twitter:player:stream" content="https://example.com/m/abc123.mp4">
<meta name="twitter:player:stream:content_type" content="video/mp4">
Serve the media from their own URLs, the same bytes to every client:
/m/<id>.gif: the GIF bytes withContent-Type: image/gifand aContent-Length. Missing files get a 404, never an HTML page with status 200./m/<id>.mp4:Content-Type: video/mp4,Accept-Ranges: bytesand correct206responses toRangerequests (Apple's players ask for byte ranges). Put themoovbox at the front (fast start).- Don't swap in GIF bytes for crawlers at the page URL by sniffing user agents: they overlap (Telegram's says “like TwitterBot”) and a cache can serve the wrong one.
WhatsApp: share the file, not the link
WhatsApp's spec describes the link card as a thumbnail, so plan for a pasted link to stay still (not tested by us). The Web Share API can hand the GIF to the share sheet as a file instead, so it goes out as media rather than a link card. We haven't recorded the result in WhatsApp yet. Fetch the file before the tap: browsers only allow navigator.share() shortly after a user gesture.
// Fetch the GIF on page load, so the tap can call share() at once.
let file = null;
fetch("/m/abc123.gif")
.then((r) => r.blob())
.then((b) => {
const f = new File([b], "abc123.gif", { type: "image/gif" });
if (navigator.canShare?.({ files: [f] })) file = f;
});
button.onclick = async () => {
if (file) return navigator.share({ files: [file], text: "Remix it: https://example.com/m/abc123" });
if (navigator.share) return navigator.share({ url: "https://example.com/m/abc123" });
await navigator.clipboard.writeText("https://example.com/m/abc123");
};
Desktop browsers without file sharing fall back to sharing or copying the link.
Making the MP4 in the browser (WebCodecs)
You don't need a server-side ffmpeg. We draw each GIF frame to a canvas, encode it with VideoEncoder as H.264 Constrained Baseline (avc1.42001f, about 1 Mbps, a keyframe every 2 seconds) and mux it with mp4-muxer using fast start. Check VideoEncoder.isConfigSupported() first; H.264 needs even width and height, so crop a pixel if needed. If the browser can't encode H.264, the file is over 4 MB, or encoding takes more than 20 seconds, we publish without an MP4 and the page simply has no og:video.
How to test
# What the crawler sees: the tags must be in the first HTML response, no JavaScript
curl -s -A "Slackbot-LinkExpanding 1.0 (+https://api.slack.com/robots)" https://example.com/m/abc123 | grep -E 'og:|twitter:'
curl -s -A "WhatsApp/2.22.20.72 A" https://example.com/m/abc123 | grep 'og:image'
# The media URLs: right type, real bytes, and a 206 for a Range request
curl -sI https://example.com/m/abc123.gif
curl -s -o /dev/null -w '%{http_code} %{content_type}\n' -H 'Range: bytes=0-1023' https://example.com/m/abc123.mp4
- Use a new URL for each attempt: apps cache previews (Slack for about 30 minutes, and it won't unfurl the same link twice in a conversation within an hour).
- Send to yourself or a private test chat, and record a few seconds of the unopened preview. One screenshot can't prove motion; two frames that differ can.
- WhatsApp's docs say to type the URL and wait up to 10 seconds for the preview to appear.
- A crawler hitting your server proves it fetched the page, not that the app animated it.
We learned this building vibe gifs, a GIF caption/remix app: gifs.agentswarm.space. Live example share page: /m/D5HcmZ8.