Create, list, update, archive and delete countdown timers programmatically. Every API key is scoped to your organization — timers you manage via the API show up right alongside the ones you make in the dashboard.
Create a key from Settings → API Tokens. The plaintext value is only ever shown once, right after creation — store it somewhere safe. Pass it as a bearer token on every request via the Authorization header:
Authorization: Bearer ft_live_4f9c2a1b...Revoking a key from the dashboard takes effect immediately — requests using a revoked key get a 401.
Every endpoint below is relative to https://api.flashtimer.com, and described in its OpenAPI document. API access is included on the Pro plan and above.
POST/v1/timers
name and expiry_datetime are the only required fields — everything else, including the entire visual theme, falls back to sane defaults. A successful response returns the new timer's id:
{
"id": "5da851e7-f41d-4af8-bd87-f45611a6bd67"
}For all available fields and a full theme example, see Field reference below.
GET/v1/timers
Returns timers belonging to your organization, newest first by default. All parameters are optional query-string parameters.
[
{
"id": "5da851e7-f41d-4af8-bd87-f45611a6bd67",
"name": "Black Friday Sale",
"description": "",
"end_date_time": "2026-11-27T23:59:00-05:00[America/New_York]",
"is_archived": false,
"created_at": "2026-01-01T00:00:00.000Z",
"updated_at": "2026-01-02T00:00:00.000Z"
}
]An evergreen, dynamic, recurring, cycle or count-up timer has no single end date (the first two end per recipient, the others never end), so its end_date_time is the placeholder 2099-01-01T00:00:00[UTC]. Retrieve the timer for its schedule.
| Parameter | Type | Description |
|---|---|---|
| query | string | Case-insensitive substring match on name. |
| status | "all" | "active" | "expired" (repeatable) | Defaults to all statuses. |
| archived | boolean | List archived timers instead of active ones. Defaults to false. |
| order_by | "name" | "created_at" | "updated_at" | "end_date_time" | Defaults to created_at. |
| order_direction | "asc" | "desc" | Defaults to desc. |
| limit | integer (1-100) | Defaults to 100. |
| offset | integer | Defaults to 0. |
GET/v1/timers/:id
Returns a timer's full configuration, including its theme — the same shape you'd send to create or update it.
{
"name": "Black Friday Sale",
"description": "",
"organization_id": "org_2abc123def456",
"expiry_datetime": "2026-11-27T23:59:00-05:00[America/New_York]",
"expiry_message": "Sale has ended!",
"language": "en",
"is_archived": false,
"theme": { "...": "same shape as the theme you sent on create" }
}PATCH/v1/timers/:id
Partial update — send only the fields you want to change. Anything you omit, including nested theme fields, is left exactly as it was — with one exception: the timer has one way of ending, so setting one of evergreen_duration, recurrence, cycle, count_up or dynamic replaces whichever of them the timer had, and drops start_on_send unless the new one is an evergreen_duration. The response is the full timer config after the update (for a token with only the timers:write scope, just { "id": "…", "updated": true }):
{
"name": "Extended Black Friday Sale",
"description": "",
"organization_id": "org_2abc123def456",
"expiry_datetime": "2026-11-29T23:59:00-05:00[America/New_York]",
"expiry_message": "Sale has ended!",
"language": "en",
"is_archived": false,
"theme": { "...": "unchanged fields are left as they were" }
}DELETE/v1/timers/:id
Permanently deletes the timer. There's no undo — if you might want it back, use Archive instead.
A successful response is 204 No Content with an empty body.
Archiving hides a timer from your active list without deleting it — useful for sales that ended but you still want to reference later.
POST/v1/timers/:id/archive
{ "archived": true }POST/v1/timers/:id/restore
{ "restored": true }POST/v1/timers/:id/send-links
A start-on-send timer (start_on_send: true with an evergreen_duration) counts down from when the email is sent, not from each recipient's first open. Call this once per send, as close to the send as you can, and use the returned url as that email's image. The deadline is sent_at plus the timer's duration, signed into the URL, so recipients can't change it. Removing the parameters shows the expired message, never a fresh countdown.
The body is optional. sent_at is an ISO 8601 date-time with an offset, within 31 days of now, and defaults to now. Needs the timers:write scope. Returns 201; 404 for an unknown timer; 422 if the timer isn't start-on-send or sent_at is out of range; or 503 if send links are temporarily unavailable.
{
"url": "https://timers.flashtimer.com/xK3mZ9pQvT.gif?d=1795960800&sig=1BCdT7WpN8niONONxc46tALkFHVyy5MG4EdodLWFWfo",
"sent_at": "2026-11-27T14:00:00.000Z",
"expires_at": "2026-11-29T14:00:00.000Z"
}A dynamic timer (dynamic with a timezone and an optional default_time) counts down to a different date for each recipient: the one your email platform puts in the image link's end parameter, usually with a merge tag for a field such as their trial end. Create it without an expiry_datetime and embed the GIF URL with ?end= followed by the date field's merge tag. It needs the Pro plan or higher, and a timer a store integration manages can't be dynamic.
The value is read in these formats. An end with no zone is read in the timer's timezone, and a date alone ends at default_time there.
| Format | Example |
|---|---|
| Date only, at default_time in the timer's timezone | 2026-10-05 |
| Date and time in the timer's timezone | 2026-10-05T18:00, 2026-10-05 18:00:30 or 2026-10-05 18:00:00.000 |
| ISO 8601 with a zone (T or a space before the time) | 2026-10-05T18:00:00+03:00, 2026-10-05 18:00:00 +0300 or 2026-10-05T15:00:00Z |
| Date and time followed by UTC or GMT, as Liquid prints a UTC time | 2026-10-05 15:00:00 UTC |
| Unix seconds (10 digits) | 1791212400 |
| Unix milliseconds (13 digits) | 1791212400000 |
| Slash date, optionally with a time. Without endfmt, only when the day can't be mistaken for the month; add &endfmt=mdy or &endfmt=dmy to read any slash date month-first or day-first | 10/25/2026, 25/10/2026 18:00 or 10/05/2026 with &endfmt=mdy |
A missing end shows the timer's expired message and fallback image, as does a date in the past. An unreadable one shows a neutral “currently unavailable” image instead, which isn't cached, and an end more than ten years away is unreadable. Your platform must output the date in one of the formats above; a literal + in an offset works even when it isn't URL-encoded.
An explicit midnight means midnight: a platform that prints a date field as 2026-10-05 00:00:00, or stores it as a midnight-UTC timestamp, ends the countdown at the start of that day. Have it output the date alone (2026-10-05) to end at default_time. Dates written with slashes (10/05/2026) are refused, since month and day order varies.
The end value is not signed: anyone holding the link can change it, which only changes their own countdown. Don't use a dynamic timer for anything that has to be enforced; use a send link for a deadline recipients can't change.
curl -X POST "https://api.flashtimer.com/v1/timers" \
-H "Authorization: Bearer ft_live_4f9c2a1b..." \
-H "Content-Type: application/json" \
-d '{ "name": "Trial ending", "dynamic": { "timezone": "Europe/Athens", "default_time": "18:00" } }'
# Embed, with your email platform's merge tag for the date field (Klaviyo shown):
# https://timers.flashtimer.com/xK3mZ9pQvT.gif?end={{ person.trial_end }}
# Each recipient's link then reads like:
# https://timers.flashtimer.com/xK3mZ9pQvT.gif?end=2026-10-05These are the body fields accepted by Create. Update accepts the exact same fields, all optional — only the ones you include are changed, except that setting one way of ending (evergreen_duration, recurrence, cycle, count_up or dynamic) replaces the one the timer had (see Update a timer).
| Field | Type | Required | Description |
|---|---|---|---|
| name | string | Yes | The timer's name (1-200 characters). |
| expiry_datetime | string | No | Zoned datetime the timer counts down to, e.g. 2026-12-31T23:59:00+02:00[Europe/Athens]. Required unless evergreen_duration, recurrence, cycle, count_up or dynamic is set, and refused alongside one. |
| description | string | No | Internal note about the timer (not shown to recipients). |
| expiry_message | string | No | Message shown once the timer has expired. |
| language | string | No | Label language: "auto" or one of en, fr, es, de, it, pt, nl. A region is dropped ("fr-CA" is stored as "fr"); anything else is refused. "auto" uses each reader's browser language, which Gmail's and Apple's image proxies hide. A lang= parameter on the image URL, such as your email platform's language merge tag, overrides the timer's language per send. Defaults to "en". |
| fallback_image_key | string | No | Key of the static image shown after expiry. Upload the image to the timer in the dashboard (Timing tab, Fallback image); GET /v1/timers/:id then returns its key. A key that isn't one of your organization's uploads is refused with 422. Requires the Basic plan or higher. |
| evergreen_duration | object | No | Counts down this long instead of to a date, e.g. { "amount": 48, "unit": "hours" } (units: minutes, hours, days, weeks; amount up to 999). By default the countdown starts at each recipient's first open. Leave expiry_datetime out; the timer's stored expiry_datetime is then 2099-01-01T00:00:00[UTC], a placeholder that is never rendered. On Update, null switches back to a fixed deadline and needs an expiry_datetime in the same request. |
| start_on_send | boolean | No | With evergreen_duration, counts from the send instead of the first open; refused without one. Embed these timers with a send link (see Send links); their plain URL shows the expired message. |
| recurrence | object | No | Resets at that time instead of expiring: { "repeat": "daily" | "weekly" | "monthly", "time": "HH:MM", "weekday"?: 0-6 (0 = Monday, required for weekly), "day_of_month"?: 1-31 (required for monthly; clamped to the month's last day), "timezone": IANA name, e.g. Europe/Athens }. A field the repeat doesn't use is ignored; an unknown key is refused. Leave expiry_datetime out. Requires the Basic plan or higher, and is refused on a timer a store integration manages. On Update, null switches back to a fixed deadline and needs an expiry_datetime in the same request. |
| cycle | object | No | Loops forever from the timer's creation: { "period_minutes": 1-1438560 }. Leave expiry_datetime out. The cycle.anchor in responses is read-only; an anchor sent in a request is ignored. Requires the Basic plan or higher, and is refused on a timer a store integration manages. On Update, null switches back to a fixed deadline and needs an expiry_datetime in the same request. Only one of evergreen_duration, recurrence, cycle, count_up and dynamic can be set. |
| count_up | object | No | Counts up from a start time instead of down to one: { "from": zoned datetime, e.g. 2026-10-05T09:00:00+03:00[Europe/Athens] }. Shows 00:00:00 until then and never stops. Leave expiry_datetime out; start_on_send is refused with it. Requires the Basic plan or higher, and is refused on a timer a store integration manages. On Update, null switches back to a fixed deadline and needs an expiry_datetime in the same request. Only one of evergreen_duration, recurrence, cycle, count_up and dynamic can be set. |
| dynamic | object | No | Counts down to an end date each recipient's link carries in its ?end= URL parameter: { "timezone": IANA name such as Europe/Athens (not an offset), "default_time": "HH:MM", default "23:59" }. Leave expiry_datetime out; start_on_send is refused with it. See Dynamic timers for the accepted ?end= formats. Requires the Pro plan, and is refused on a timer a store integration manages or one in an A/B test (an A/B test's embed carries no recipient's date). On Update, send timezone each time; a default_time left out keeps the timer's current one ("23:59" for a timer switching to dynamic), and null switches back to a fixed deadline and needs an expiry_datetime in the same request. Only one of evergreen_duration, recurrence, cycle, count_up and dynamic can be set. |
| theme | object | No | Visual overrides — see below. Any field you omit falls back to the default theme. |
All optional. Unknown keys are rejected with a 422 so typos don't silently fall back to defaults.
| Field | Type | Description |
|---|---|---|
| shape_type | "ft_01" | "ft_02" | "ft_03" | "ft_04" | "ft_05" | "ft_06" | Which of the six timer visual templates to use. |
| font_size | "small" | "medium" | "large" | Digit font size. |
| font | string | A font id, not a name: one of the system fonts or a font your organization uploaded. It's the last part of the font's address under Fonts (/fonts/<id>). An id that matches no such font is refused with 422. Choosing a font your organization uploaded needs the Basic plan or higher; without it the request returns 403 with code plan_feature_required. |
| backdrop_fill | hex color | Background fill color, e.g. #0F172A. |
| no_backdrop | boolean | Make the background transparent instead of filling it with backdrop_fill. A GIF pixel is either transparent or solid, so the timer's antialiased edges are blended into matte_color. Requires the Basic plan or higher; turning it on without one returns 403 with code plan_feature_required. |
| matte_color | hex color | With no_backdrop, the colour the timer's edges are blended into. Set it to the email background behind the timer, or the edges show a fringe of this colour. Defaults to #FFFFFF. |
| message_color | hex color | Colour of expiry_message's text once the timer has ended. Leave it out to use digits.color. |
| digits.leading_zero | boolean | Pad single digits with a leading zero. |
| digits.color | hex color | Digit text color. |
| digits.fill | hex color | Digit background fill color. |
| digits.border_color | hex color | Optional digit border color. |
| labels.color | hex color | Color of the unit labels (days/hours/etc). |
| labels.display | boolean | Whether unit labels are shown. |
| separators.color | hex color | Color of the separators between units. |
| separators.display | boolean | Whether separators are shown. |
| days.display | boolean | Whether the days unit is shown. |
Error responses you may get from any /v1/timers endpoint, with an example body for each. Toggle Show Schema to see the shape instead of example values.
{
"error": "expiry_datetime must be a valid zoned datetime string"
}Requests are limited per organization (not per key — all keys belonging to the same organization share the same limit) by plan: 60 per minute on Pro, 150 per minute on Business and 300 per minute on Enterprise. The Free and Basic plans don't include API access. Once you hit the limit, you'll get a 429 until the window resets.
flashtimer also exposes an MCP server at https://mcp.flashtimer.com/mcp, so AI assistants like Claude and ChatGPT can create and manage your timers directly. It's available on the Pro plan and up, with the same rate limit as the REST API.
Setting it up for a marketing team? The guide to flashtimer in AI assistants has the steps for Claude and ChatGPT, example requests and troubleshooting.
https://mcp.flashtimer.com/mcp and select Add.Claude acts as you, in the flashtimer organization you choose. Disconnect it at any time in Settings → Connected Apps. On Claude Team or Enterprise, an Owner of your Claude organization adds the connector first under Claude's Organization settings → Connectors (Add, then Custom); each member then selects Connect under Customize → Connectors.
In Claude Code, add it with this command, then run /mcp in Claude Code and follow the steps in your browser to sign in:
claude mcp add --transport http flashtimer https://mcp.flashtimer.com/mcphttps://mcp.flashtimer.com/mcp, choose OAuth authentication, confirm the warning and select Create as a plugin.The OpenAI Responses API calls the server itself: pass an API token as the authorization of an mcp tool. OpenAI doesn't store it, so send it with every request.
curl https://api.openai.com/v1/responses \
-H "Authorization: Bearer $OPENAI_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "gpt-5",
"tools": [
{
"type": "mcp",
"server_label": "flashtimer",
"server_url": "https://mcp.flashtimer.com/mcp",
"authorization": "ft_live_4f9c2a1b...",
"require_approval": "always"
}
],
"input": "List my flashtimer timers"
}'Clients that take a fixed header, like Cursor or VS Code, can use an API token instead: pass it as a bearer token in the Authorization header of your client's MCP config. A token with only the read scope gets the read tools.
{
"mcpServers": {
"flashtimer": {
"url": "https://mcp.flashtimer.com/mcp",
"headers": {
"Authorization": "Bearer ft_live_4f9c2a1b..."
}
}
}
}Once it's connected, ask in your own words. For example:
Tools that create, revise or fetch a timer also return its image URL, a paste-ready HTML snippet for your email and a preview image, so the assistant can hand you the code and see what it made.
In Claude and ChatGPT the result also appears as a card: the timer running, with its texts, colours, end date, font and shape to change and preview before you save, and a button that copies the email snippet.
An assistant can read a timer linked to a store discount but not change, archive or delete it (it can still set or clear the timer's stock signal); it says where to change it instead. A timer your store syncs follows the discount on its own; one you linked by hand takes on new dates when it's resynced.
A/B tests and stock signals need the Business plan: starting or resuming a test, adding a click destination, listing store products and setting a stock signal. Reading a test, pausing it, promoting its winner and clearing a signal work on Pro too.
| Tool | Access | Description |
|---|---|---|
| create_timer | Write | Creates a new timer. Same fields as POST /v1/timers. |
| list_timers | Read | Lists your timers, with optional filtering, ordering, and pagination. |
| get_timer | Read | Fetches the full configuration of a single timer by id. |
| archive_timer | Write | Archives a timer so it stops showing as active. |
| restore_timer | Write | Restores a previously archived timer. |
| create_send_link | Write | Mints the embed URL for one send of a start-on-send timer. Same as POST /v1/timers/:id/send-links. |
| design_timer | Write | Designs a new timer's settings from a free-text brief, e.g. "20% off, ends Friday, dark theme". It picks settings; it doesn't create images. |
| revise_timer | Write | Changes an existing timer's settings (texts, colours, layout, or end date) from a free-text instruction, e.g. "make it red and extend by 2 days". A recurring, cycle, count-up, evergreen or dynamic timer has no shared end date, so it keeps its schedule: the result's expiry_datetime is null, with a note when a new end date was asked for. |
| update_timer | Write | Changes exactly the fields you send on a timer. Same fields as PATCH /v1/timers/:id. |
| duplicate_timer | Write | Copies a timer into a new one named "<name> (Copy)". |
| delete_timer | Write | Deletes a timer for good. Prefer archive_timer to just hide it. |
| list_promotions | Read | Lists the active and upcoming discounts of your connected stores (Shopify, WooCommerce, BigCommerce, PrestaShop, Magento, Squarespace). |
| create_timer_from_promotion | Write | Creates a timer for a store discount, in your brand kit's style, linked to it (resync it when the discount's dates change). |
| link_timer_to_promotion | Write | Links an existing timer to a store discount; the timer takes on its dates. |
| resync_timer_to_promotion | Write | Gives a timer linked to a store discount by hand that discount's current dates, like Resync in the app. |
| get_brand_kit | Read | Your brand kit's colours and font, which new timers start in. |
| list_fonts | Read | The fonts a timer can use, by name, with the id to pass as theme.font. |
| get_usage | Read | This billing period's timer views against your plan's limit plus any top-up credit, and whether timers have gone static. |
| get_analytics | Read | Views, uniques, clicks, top timers, countries, email clients and devices for a range. |
| list_stock_products | Read | A connected store's products and stock. |
| set_inventory_signal | Write | Expires a timer when a product's stock reaches a threshold (confirm first if it already has). |
| clear_inventory_signal | Write | Stops watching stock. |
| list_ab_tests | Read | Your A/B tests. |
| get_ab_test | Read | A test's results, embed snippet and tracked links. |
| create_ab_test | Write | Starts an A/B test between two timers. |
| set_ab_test_active | Write | Pauses or resumes a test. |
| promote_ab_test_winner | Write | Ends a test and serves the winner to everyone (confirm first). |
| add_ab_click_destination | Write | Allows a URL for the test's tracked link. |
| list_campaigns | Read | Campaigns on your connected email platforms. |
| preview_campaign_embed | Write | Shows what putting a timer into a campaign will do, with a confirmation token. |
| embed_timer_in_campaign | Write | Writes the timer into the campaign, with the token. |
Still stuck? Contact support.
Add one or more webhook URLs to a timer and flashtimer will POST a JSON payload to each URL once that timer's countdown reaches zero — usually within five minutes, later when many timers expire at the same time. Use this to trigger Zapier Zaps, Make scenarios, or any custom automation endpoint — without writing any server-side code.
flashtimer sends this JSON body with Content-Type: application/json:
{
"event": "timer.expired",
"timer": {
"id": "5da851e7-f41d-4af8-bd87-f45611a6bd67",
"name": "Black Friday Sale",
"expiredAt": "2026-11-27T23:59:00.000Z",
"organizationId": "org_01jk..."
},
"timestamp": "2026-11-27T23:59:01.234Z"
}Both platforms expose a "catch webhook" trigger URL you can paste directly into the dialog:
Connect Klaviyo so flashtimer can list your draft and scheduled campaigns inside the Deploy → Send to ESP panel. Pick a campaign and click Embed Timer: flashtimer adds the timer to the campaign for you, with a preview first — no tab-switching needed.
pk_. Klaviyo shows it only once.<img> snippet pointing at the live timer GIF on timers.flashtimer.com.Add a Custom HTML block in your email and paste:
<img src="https://timers.flashtimer.com/xK3mZ9pQvT.gif"
alt="Black Friday Sale"
style="display:block;outline:none;border:none;text-decoration:none;width:100%;" />Connect Mailchimp to pull draft campaigns into the Send to ESP panel and generate a countdown embed in seconds.
.api.mailchimp.com in your API endpoint URL, e.g. us1.save (draft) are listed in the Send to ESP panel.Add a Code block in your campaign and paste:
<img src="https://timers.flashtimer.com/xK3mZ9pQvT.gif"
alt="Black Friday Sale"
style="display:block;outline:none;border:none;text-decoration:none;width:100%;" />Omnisend works by pasting the timer into your email: there's nothing to connect. Open the timer, click Embed and copy its HTML. For an evergreen or dynamic timer, choose Omnisend as the email platform there, so the URL carries Omnisend's tag, such as [[contact.email]].
Add an HTML block in your campaign and paste:
<img src="https://timers.flashtimer.com/xK3mZ9pQvT.gif"
alt="Black Friday Sale"
style="display:block;outline:none;border:none;text-decoration:none;max-width:100%;" />Connect ActiveCampaign to see draft and scheduled email campaigns and embed countdown timers — useful for time-sensitive automation sequences.
https://youraccount.api-us1.com) and your API Key are on that page.0 and 1) are listed in the Send to ESP panel.Add an HTML block in your campaign and paste:
<img src="https://timers.flashtimer.com/xK3mZ9pQvT.gif"
alt="Black Friday Sale"
style="display:block;outline:none;border:none;text-decoration:none;max-width:100%;" />Connect Drip to embed countdown timers in your broadcasts and workflows. Requires your API token and your numeric Account ID.
app.getdrip.com/{accountId}/....Add a Custom HTML block in your broadcast and paste:
<img src="https://timers.flashtimer.com/xK3mZ9pQvT.gif"
alt="Black Friday Sale"
style="display:block;outline:none;border:none;text-decoration:none;max-width:100%;" />Connect Brevo (formerly Sendinblue) to pull draft and scheduled email campaigns into flashtimer and embed countdown timers with one click.
xkeysib-.Add a Raw HTML block in your campaign and paste:
<img src="https://timers.flashtimer.com/xK3mZ9pQvT.gif"
alt="Black Friday Sale"
style="display:block;outline:none;border:none;text-decoration:none;max-width:100%;" />Connect Wix to pull your draft Email Marketing campaigns into flashtimer and add a countdown timer to one made in Wix's HTML email editor in one click. Requires a Wix API key and your Site ID.
manage.wix.com/{siteId}/....In Wix's campaign editor, add an HTML element and paste:
<img src="https://timers.flashtimer.com/xK3mZ9pQvT.gif"
alt="Black Friday Sale"
style="display:block;outline:none;border:none;text-decoration:none;max-width:100%;" />Constant Contact works by pasting the timer into your email: there's nothing to connect. Open the timer, click Embed and copy its image URL or HTML. For an evergreen timer, choose Constant Contact as the email platform there, so the URL carries [[emailAddress]], and paste the HTML into a custom code email: Constant Contact doesn't always fill in the tag in an image URL. Test with two contacts: each should see their own countdown.
In an email built in the drag-and-drop editor, add an Image block, choose Image URL and paste the timer's image URL, such as https://timers.flashtimer.com/xK3mZ9pQvT.gif. Paste the address rather than uploading the GIF: an uploaded copy stops counting down.
In a custom code email, paste this where the timer belongs:
<img src="https://timers.flashtimer.com/xK3mZ9pQvT.gif"
alt="Black Friday Sale"
style="display:block;outline:none;border:none;text-decoration:none;max-width:100%;" />Connect GetResponse to pull draft newsletters into flashtimer and embed countdown timers without leaving the timer editor.
draft) are listed in the Send to ESP panel.Add a Custom HTML block in your newsletter and paste:
<img src="https://timers.flashtimer.com/xK3mZ9pQvT.gif"
alt="Black Friday Sale"
style="display:block;outline:none;border:none;text-decoration:none;max-width:100%;" />Connect MailerLite to pull draft email campaigns into flashtimer and embed countdown timers in one click. Requires a MailerLite Advanced plan for API access.
Add an HTML block in your campaign and paste:
<img src="https://timers.flashtimer.com/xK3mZ9pQvT.gif"
alt="Black Friday Sale"
style="display:block;outline:none;border:none;text-decoration:none;max-width:100%;" />Connect Zapier to send timer events to any of Zapier's 7,000+ supported apps — Slack, Notion, Google Sheets, Airtable, Salesforce, and more. No code required. flashtimer POSTs a JSON payload to your Zap the moment you click Send timer data in a timer's Send to ESP panel.
https://hooks.zapier.com/hooks/catch/...).flashtimer sends this JSON to your Zap when you click Send timer data:
{
"timerName": "Black Friday Sale",
"timerUrl": "https://timers.flashtimer.com/xK3mZ9pQvT.gif",
"gifUrl": "https://timers.flashtimer.com/xK3mZ9pQvT.gif",
"expiresAt": "2026-11-27T23:59:00-05:00[America/New_York]"
}timerUrl and gifUrl point at the live GIF — embed either directly in a Slack message or HTML email action. expiresAt is null for an evergreen, recurring, cycle, count-up or dynamic timer, which has no single end date. (Evergreen timers used to send the placeholder 2099-01-01T00:00:00[UTC] here.)
Connect your Shopify store so flashtimer can read your active Price Rules and surface real sale end-dates directly in the timer editor. Instead of typing a deadline manually, you pick a discount rule and the countdown is set automatically.
Since January 1, 2026 Shopify no longer lets stores create new custom apps under Develop apps. The steps below work for a custom app your store already has; if yours doesn't, contact info@byte2bit.com and we'll help you connect.
read_discounts — required to list your discounts in the timer editor and keep their timers in sync: flashtimer reads each discount when Shopify reports a change, and registers its webhook listeners automatically (Shopify doesn't require a separate scope for that). Without it, no discount syncs.read_price_rules — also required to list your discounts: flashtimer reads the ones made through Shopify's older Price Rules API too, so their timers still match.read_products — required for the Shopify Stock Monitor feature (list products and stock levels in the timer editor).read_inventory — required for the Stock Monitor to read per-location inventory quantities.read_rollouts — also required to list and sync your discounts: flashtimer checks whether a Rollout decides who gets each one, and leaves that discount's timer as it is while one does. A token without it isn't accepted.shpat_) — Shopify shows it only once..myshopify.com domain from the admin URL.ends_at date appear in the timer editor under Pick a sale end date.ends_at: null (always-on) are shown as context but don't set a deadline.// Example discounts returned from Shopify
[
{ "id": "123456789", "title": "Black Friday 20% Off", "endsAt": "2026-11-28T00:00:00Z" },
{ "id": "987654321", "title": "Cyber Monday", "endsAt": "2026-12-01T00:00:00Z" },
{ "id": "111222333", "title": "Always-on member deal","endsAt": null }
]When you save your Shopify credentials, flashtimer automatically registers webhook listeners in your store for discounts/create, discounts/update, and discounts/delete. The legacy Price Rules API (price_rules/create, price_rules/update, price_rules/delete) is also supported for stores that haven't migrated to the modern Discounts API.
Webhook sync is off by default. Under Settings → Integrations → Shopify you'll find three toggles under Automation:
Every incoming webhook is verified with HMAC-SHA256 using your Shopify Custom App's API Secret Key. Add it to the Webhook Signing Secret field in the Shopify integration settings — without it, flashtimer will reject all incoming webhook calls. The API Secret Key is on the API credentials tab of your Custom App in Shopify Admin.
Webhooks are received at:
POST https://api.flashtimer.com/webhooks/shopifyThe Stock Monitor lets you tie any timer to a Shopify product's inventory. When the product's total stock drops to or below a threshold you define, flashtimer automatically expires the timer — every email opened after the sell-out shows the expiry message instead of a live countdown.
Your Shopify Custom App must have two extra scopes beyond the base integration:
read_products — allows flashtimer to list your products and their stock levels.read_inventory — allows flashtimer to read per-location inventory quantities.read_products and read_inventory, then click Save.0 to expire only when completely sold out, or 10 for a low-stock trigger), and click Save.The first time your store fires an abandoned checkout, flashtimer automatically creates one starter evergreen timer named "Abandoned Cart Recovery" (a 30-minute countdown that starts fresh for each customer the moment they open the email) so you have something ready to drop into your own cart-recovery email flow right away. This only ever happens once per store — if you delete the timer it will not be re-created.
flashtimer never reads or stores anything from the checkout itself — no customer email, name, cart contents, or address. The webhook is used purely as a signal that a checkout was abandoned; the per-recipient countdown runs entirely through the same evergreen merge-tag mechanism used by every other evergreen timer.
checkouts/create is one of Shopify's protected-data webhook topics — Shopify will not deliver it on a live store until this is granted, regardless of scopes. For a Custom App connection, request it yourself under your app's API access settings in Shopify Admin; it does not require anything from flashtimer. Until it's granted, this feature is silently inactive rather than broken — everything else in the Shopify integration is unaffected.read_checkouts and read_orders — required by Shopify to subscribe to checkout webhook topics. Add them the same way described above for Stock Monitor, then reconnect.Connect Make (formerly Integromat) to send timer data to any of Make's 2,000+ supported apps and services — CRMs, project tools, databases, and more — using a visual no-code workflow. No code required.
{
"timerName": "Black Friday Sale",
"timerUrl": "https://timers.flashtimer.com/xK3mZ9pQvT.gif",
"gifUrl": "https://timers.flashtimer.com/xK3mZ9pQvT.gif",
"expiresAt": "2026-11-27T23:59:00-05:00[America/New_York]"
}expiresAt is null for an evergreen, recurring, cycle, count-up or dynamic timer, which has no single end date. (Evergreen timers used to send the placeholder 2099-01-01T00:00:00[UTC] here.)
Connect your WooCommerce store to surface active sale end dates from your coupons directly in the timer editor. Instead of entering a deadline manually, pick a coupon and flashtimer fills it in automatically.
https://yourstore.com), Consumer key, and Consumer secret under Settings → Integrations → WooCommerce.date_expires are listed in the timer editor under Pick a sale end date.on_sale=true) are also surfaced alongside coupons, so you can link a timer to a product sale end date too.[
{ "id": 101, "name": "Black Friday 20% Off", "date_expires": "2026-11-28T00:00:00Z" },
{ "id": 102, "name": "Cyber Monday Flash", "date_expires": "2026-12-01T00:00:00Z" }
]When you save your WooCommerce credentials, flashtimer automatically registers webhook listeners in your store for coupon.created, coupon.updated, coupon.deleted, and coupon.restored via the WooCommerce REST API. Each webhook is signed with a per-org HMAC-SHA256 secret that flashtimer generates automatically — no manual configuration needed.
Webhook sync is off by default. Under Settings → Integrations → WooCommerce you'll find three toggles under Automation:
date_expires_gmt is added.Webhooks are received at:
POST https://api.flashtimer.com/webhooks/woocommerce/{orgId}The orgId is your organization's unique identifier — flashtimer uses it to look up your credentials and route the webhook to the correct account. It's included automatically in the webhook URL registered in your WooCommerce store.
Connect your BigCommerce store to see active promotion and coupon end dates in the timer editor. flashtimer reads your store's promotions via the V2/V3 API so you can align countdowns to your discount expiry without switching tabs.
https://api.bigcommerce.com/stores/{storeHash}/v3/.expires date are listed in the timer editor.[
{ "id": 501, "name": "Black Friday Promo", "expires": "2026-11-28T00:00:00+00:00" },
{ "id": 502, "name": "Holiday Sale", "expires": "2026-12-26T00:00:00+00:00" }
]These platforms work the same way as Klaviyo or Mailchimp above: connect them under Settings → Integrations with the credentials the dialog asks for, then open a timer's Send to ESP panel, pick a campaign (a draft, or a scheduled one where the platform lets it be changed) or a template and click Embed Timer.
HubSpot, Campaign Monitor, ConvertKit, Iterable, Beehiiv, SendGrid, Mailgun, Postmark, Mailjet, MoEngage, Marketo, Amazon SES.
For these, copy the timer's ready-made HTML snippet from Embed in the timer editor and paste it into your campaign: Pardot, AWeber, Braze, Customer.io, Bloomreach, CleverTap, Keap, Emma, Zoho Campaigns, Retention.com.
Any other email platform works too: copy the timer's image URL or HTML from Embed in the timer editor and paste it into an image or HTML block.
curl https://api.flashtimer.com/v1/timers \
-H "Authorization: Bearer ft_live_4f9c2a1b..."