We recently added two major sections to our website: a blog and dedicated feature spotlight pages.
Integrating the blog into the rest of our site while keeping publishing flexible meant building a new content pipeline. We'll cover that in another post. When we started building its Open Graph template in our visual template editor, we discovered a glaring oversight: image URLs were templatable in the underlying model, but we had never wired up the editor UI.
Our feature pages presented a different challenge. We put a lot of effort into giving each page its own hero treatment, diagram and supporting visuals. We wanted the OG images to capture that work, but recreating every page inside an image template was not realistic.
Fortunately, HCTI's signed create-and-render URLs were a perfect fit.
Using our own product for both approaches worked well. It also showed us a few places where HCTI could be better.
Blog Post OG Images
Each post starts with one of the whimsical, AI-generated illustrations that have become part of the blog's visual style. They work well at full size, where the title and article around them provide context.

They worked at the top of an article, but not nearly as well on their own in Messages, Twitter or Slack. Social platforms could crop the artwork unpredictably, and without a title, date or HCTI branding, the image felt disconnected from the post. We wanted to keep the personality of the illustrations while making the social cards feel intentional, not like AI slop with text pasted over it.
The template gives every post the same deliberate treatment: an HCTI-green canvas, a faint version of the illustration in the background, and textual metadata layered above it. The art gives a bit of texture and personality while the standardized layout, typography and branding make it feel polished.
The title, date and tags could already change with each render. The missing piece was the background image. Image URLs already supported template variables in the underlying model, but the editor had no UI for them.
The image block's URL control now uses the same variable popover as other templatable properties. For this template, we connected it to bg_image_url. The editor stores the variable key and its requirement mode beside the typed URL, rather than putting {{bg_image_url}} into a string.
The Variables panel includes bg_image_url in the template's input contract. We can enter a sample URL in the test payload and see the background update directly on the canvas, without asking the server to render a new image.
At render time, the publishing pipeline sends the post metadata and image URL in one ordinary JSON payload:
{
"title": "Post title",
"date": "August 4, 2026",
"tags": ["product", "engineering"],
"bg_image_url": "https://example.com/post-image.png"
}
As with other templatable properties, the image can be required, fall back to the URL saved in the design or be ignored when absent. The variable inventory catches missing required values and incompatible reuse before rendering.
Once those pieces were connected, our publishing pipeline could pass each post's image into a single reusable OG template.

The template's variable panel includes bg_image_url alongside title, date and tags, so the editor can preview the same inputs our publishing pipeline supplies at render time.
This was a small missing piece, but exactly the kind of gap that becomes obvious when you use a product for a real workflow.
Feature Page OG Images
The feature pages worked surprisingly well out of the box.
Instead of recreating their designs in a template, we gave HCTI the page URL, targeted the hero section with selector and set the browser viewport to 1200 by 630. That gives the hero an OG-friendly width and a useful starting height without limiting the final capture to 630 pixels.
var ogImageUrl = client.CreateAndRenderUrl(new CreateUrlImageRequest
{
Url = featurePageUrl,
Selector = "#fp-hero",
ViewportWidth = 1200,
ViewportHeight = 630,
MsDelay = 200
});
selector captures the hero's full bounds, a taller hero can render taller than 630 pixels. We also use ms_delay to wait 200 milliseconds because some feature heroes finish applying their styles in JavaScript.Tip
Using .NET? The HtmlCssToImage package can generate signed image URLs directly. HtmlCssToImage.Blazor provides OG image components for Blazor, while HtmlCssToImage.TagHelpers supports Razor Pages and MVC.
That got us most of the way there.

Again, good but not great.
The full navigation did not need to be in an OG image, and neither did the two calls to action. I wanted to keep a small element of HCTI branding, reclaim that space and let the hero stand on its own.
We could have taken the easy way out: add a query parameter, look for it in the application and apply some custom CSS. That would have worked.
But it would also have made screenshot mode an application-specific URL convention. The request already knew why it was there; the page should be able to know too.
Enter identify_as_hcti
The premise is simple: let the page know when HCTI is rendering it as a screenshot.
Setting identify_as_hcti to true adds a header to the top-level page request:
X-HCTI-SCREENSHOT: 1
For us, enabling it meant adding a single property to the request:
var ogImageUrl = client.CreateAndRenderUrl(new CreateUrlImageRequest
{
Url = featurePageUrl,
Selector = "#fp-hero",
ViewportWidth = 1200,
ViewportHeight = 630,
MsDelay = 200,
IdentifyAsHcti = true
});
IdentifyAsHcti adds the screenshot header without requiring a special query parameter or alternate page URL.On our site, a small HttpContext extension turns the header into a boolean that we can use anywhere in any page or component lifecycle:
if (HttpContext.IsHctiScreenshotRequest())
{
// Render the screenshot-specific version of the page.
}
Now screenshot requests can hide the normal navigation and calls to action, adjust the hero spacing and render a small HCTI-branded element. Ordinary visitor requests still get the original page.

The difference is subtle, which is the point. The page still does all of the design work; HCTI just gives it enough context to remove the parts that only make sense when the page is interactive.
Boom. A clean, purpose-built OG image generated directly from the page we had already designed.
One important note: X-HCTI-SCREENSHOT: 1 is a public, predictable value. It is useful for styling, content selection and logging, but it should not be treated as authentication.
Getting Fancy: Custom Headers Bring a Whole New Capability
I resisted an initial urge for scope creep and shipped the narrow identify_as_hcti parameter. The new value only had to travel through image creation and persistence into our browser service, so it took a few minutes to wire up. Then I went back to the feature pages. We could not ship without pristine OG images, after all.
A couple of days later, after the new feature pages were live, I was still thinking about it. The OG workflow did not need anything more than a boolean, but the mechanism pointed at a broader solution: let developers attach their own HTTP headers to a URL screenshot request.
That also gave us a better answer to a longstanding customer question: how can a protected page admit HCTI without allowlisting a fixed set of IP addresses? Fixed egress addresses are a poor fit for a rendering system that scales across serverless and containerized workers like ours. With custom headers, a customer can supply a short-lived token or private value that their application validates before serving the page. The customer controls the credential and its scope; HCTI simply carries it with the request.
That became the new headers parameter.
{
"url": "https://example.com/feature",
"headers": {
"X-Preview-Mode": "enabled"
}
}
Custom headers can select a rendering mode, identify preview traffic, authenticate to a protected page you control or pass other controlled application state without HCTI needing a new parameter for every use case.
Tip
If a site you control uses Cloudflare and you're getting "Are you human?" screenshots, a private header can let a narrowly scoped Cloudflare rule identify authorized renders. This allows screenshots through without disabling challenges for ordinary traffic. Follow the Cloudflare challenges debugging guide to set it up.
Keeping Custom Headers Contained
Headers can contain sensitive information, so the defaults matter.
By default, custom headers are sent only with top-level navigations to the requested URL's origin. They are not automatically attached to images, scripts, API calls or other subrequests. If another origin needs the headers, that exact origin must be explicitly allowed with additional_header_origins. Sending headers with subrequests also requires explicitly enabling include_headers_on_subrequests.
Suppose HCTI is rendering https://example.com/report. The page loads its application code from https://assets.example.com, while https://tracking.example.com proxies requests to a third-party analytics service.
Forwarding custom headers to every request would expose them to every third party on the page. Allowing the requested origin and all of its subdomains sounds more reasonable, but it would still send the headers to tracking.example.com and potentially to the analytics provider behind it.
That is why additional_header_origins is an explicit allowlist. If only the asset server needs the headers, the request can say exactly that:
{
"url": "https://example.com/report",
"headers": {
"X-Render-Token": "short-lived-token"
},
"additional_header_origins": [
"https://assets.example.com"
],
"include_headers_on_subrequests": true
}
https://example.com/report headers sent (top-level)
├── https://assets.example.com headers sent
├── https://tracking.example.com headers withheld
└── https://third-party.example headers withheld
The allowlist matches exact origins, including the scheme, hostname and port, so you control precisely where the headers can go.
If a custom header carries a credential, keep it short-lived and narrowly scoped. Definitely do not embed it in a user-facing or client-side create-and-render URL. URLs are sometimes recorded by internal logs and monitoring systems too, so an authenticated POST /v1/image request is preferable when a header contains a secret.
Read more in the headers docs.
Looking to secure your screenshots further?
- Storage Destinations can store finished images in your own S3 or S3-compatible bucket. Choose Disable HCTI Storage to keep the finished files only in your bucket and disable public serving through HCTI.
- HTTP proxies route render traffic through a proxy you control for stable egress and access to restricted content.
- Visit our Security page for a broader look at how HCTI protects your images and data.
Improving HCTI by Using HCTI
We use the same production version of HCTI that is available to our customers.
We are not even especially exotic power users. Most of the time, we are trying to accomplish the same things everyone else is: create a good image, automate it and move on to the next part of the project.
That is what makes using HCTI ourselves so useful. When we encounter an awkward edge or a missing piece, there is a good chance someone else has felt it too.
In this case, producing OG images for two new parts of our website resulted in:
- Templatable image URLs in the visual template editor.
- A simple way for pages to recognize HCTI screenshot requests.
- Flexible custom headers for more advanced URL-rendering workflows.
- Some extremely pristine OG images.
Not bad for a task that started with, "We should probably make some social cards."


