Finance (Banks), Opening hours 1.0.0
| Template | Finance (Banks), tpl_c43b789e739a |
|---|---|
| Variant | Opening hours |
| Version | 1.0.0, the newest: what a channel following this variant draws |
| Shapes | 9x16, 4x5, 1x1, 16x9, 2x3 |
| Length | 14 seconds |
Example video
The 1x1 cut of this version, drawn from its own worked example. Open the video on its own.
What it accepts
A branch's opening hours or a holiday change: the headline, the branch and its address, and one to fourteen days with their hours on dotted leaders, complete on the first frame, each day then lit in turn, closed days quieter, then a notice, the button and your presenter. The legal text stays on throughout. Eight seconds with one day, 0.8 seconds more per further day. metaData is never drawn.
| Field | Type | Required | Limit | What it is |
|---|---|---|---|---|
clipData | group | yes | Clip data: Everything drawn in the clip: the section, the headline, the branch and its address, the timetable, the notice, the call to action and the legal text. | |
clipData.section | text | up to 24 characters | Section: What the post is about, set small in capitals at the end of the header, such as Branches or Opening hours. | |
clipData.headline | text | yes | up to 56 characters | Headline: The title of the post. |
clipData.branch | text | up to 48 characters | Branch: The branch the hours are for, beside a place mark under the headline. Leave out for every branch. | |
clipData.address | text | up to 80 characters | Address: The branch's address, under its name. | |
clipData.days | list | yes | at least 1, up to 14 | Days: One to fourteen days in the order they are shown, each with its hours on a line of a timetable. Where the frame is short of height they turn pages. |
clipData.days[] | group | Day | ||
clipData.days[].day | text | yes | up to 24 characters | Day: The day as it should be shown, such as Mon 22 Dec or Saturdays. |
clipData.days[].hours | text | yes | up to 24 characters | Hours: The hours as they should be shown, such as 9:00 to 16:00, or Closed. |
clipData.days[].closed | boolean | Closed: The branch is closed that day: the line is drawn quieter. | ||
clipData.days[].note | text | up to 40 characters | Note: A short word on the day, such as Closes early, marked in your brand colour. | |
clipData.notice | text | up to 140 characters | Notice: A line after the timetable, in a quiet framed note, such as what stays open while the branch is closed. | |
clipData.cta | group | Call to action: The button at the end and the line under it, such as a web address or a phone number. | ||
clipData.cta.label | text | yes | up to 28 characters | Button: What the button says. |
clipData.cta.detail | text | up to 48 characters | Line under it: An address, a phone number, a branch. | |
clipData.legal | text | up to 260 characters | Legal text: What must be shown with the post, such as who supervises the bank and how deposits are protected. On screen from the first frame to the last. | |
clipData.look | group | Look: How the clip looks and moves. The same six switches in every format (theme, pace, corners, alignment, logo, logo size). Leave any out for the package's default: the brand's colours, calm, square corners, text at the start. | ||
clipData.look.theme | enum | brand, light, dark, bold | Theme: Brand: your brand's own colours and background. Light or dark: a page tinted with your brand colour. Bold: your brand colour as the page. | |
clipData.look.pace | enum | calm, standard, punchy | Pace: How things arrive: calm fades, a standard rhythm, or punchy slams and hard cuts. | |
clipData.look.corners | enum | square, soft, round | Corners: The shape of cards, pictures and buttons. | |
clipData.look.align | enum | start, centre | Text alignment: Where the text lines up. | |
clipData.look.logo | enum | auto, free | Logo: The style's own: the logo on a small plate so it reads on any page. As it is: the logo whole, with no plate, frame or crop, for a logo made to stand on the page, such as one with a transparent background. As it is, make sure your logo reads on the page of the theme you choose. | |
clipData.look.logoSize | enum | auto, small, large | Logo size: Standard is the style's own size. Small draws the logo a fifth smaller, with its plate. Large draws it a quarter larger wherever the style has room for it, and at the standard size where it has none. | |
metaData | group | Meta data: Everything about the post that is never drawn in the clip: its title, its description in four lengths, one per kind of platform, and its hashtags. Reelwire adds the brand's description, link and hashtags after it. Left out, the post text is generated from the clip data. | ||
metaData.title | text | up to 100 characters | Title: Used where the platform has a title of its own: Facebook Feed, LinkedIn, Pinterest and YouTube. Up to 100 characters, which every one of them takes. | |
metaData.captionTiny | text | up to 200 characters | Tiny description (Pinterest, Threads, X): Up to 200 characters. The brand's description, link and hashtags follow it within the platform's limit. | |
metaData.captionShort | text | up to 500 characters | Short description (Telegram, Threema, WeChat, WhatsApp): Up to 500 characters. The brand's description, link and hashtags follow it within the platform's limit. | |
metaData.captionMedium | text | up to 1500 characters | Medium description (Discord, Facebook Reels, Instagram, LinkedIn, Signal, Slack, TikTok): Up to 1500 characters. The brand's description, link and hashtags follow it within the platform's limit. | |
metaData.captionLong | text | up to 4000 characters | Long description (Facebook Feed, YouTube): Up to 4000 characters. The brand's description, link and hashtags follow it within the platform's limit. | |
metaData.hashtags | list | up to 15 | Hashtags: Written without the #. Up to 15, which every platform takes. | |
metaData.hashtags[] | text | up to 60 characters | Hashtag |
locales carries other languages: under each language code, the parts of clipData and metaData that differ, laid over the default, so a language with no copy still renders.
A picture is always a file from your media library: media:// followed by its id or its name.
Anything the table does not list is refused, and so is anything over its limit, with a 400
schema_validation error that names the field.
The JSON Schema
{
"type": "object",
"properties": {
"clipData": {
"type": "object",
"properties": {
"section": {
"type": "string",
"maxLength": 24,
"title": "Section",
"description": "What the post is about, set small in capitals at the end of the header, such as Branches or Opening hours."
},
"headline": {
"type": "string",
"maxLength": 56,
"title": "Headline",
"description": "The title of the post."
},
"branch": {
"type": "string",
"maxLength": 48,
"title": "Branch",
"description": "The branch the hours are for, beside a place mark under the headline. Leave out for every branch."
},
"address": {
"type": "string",
"maxLength": 80,
"title": "Address",
"description": "The branch's address, under its name."
},
"days": {
"type": "array",
"items": {
"type": "object",
"properties": {
"day": {
"type": "string",
"maxLength": 24,
"title": "Day",
"description": "The day as it should be shown, such as Mon 22 Dec or Saturdays."
},
"hours": {
"type": "string",
"maxLength": 24,
"title": "Hours",
"description": "The hours as they should be shown, such as 9:00 to 16:00, or Closed."
},
"closed": {
"type": "boolean",
"title": "Closed",
"description": "The branch is closed that day: the line is drawn quieter."
},
"note": {
"type": "string",
"maxLength": 40,
"title": "Note",
"description": "A short word on the day, such as Closes early, marked in your brand colour."
}
},
"required": [
"day",
"hours"
],
"additionalProperties": false,
"title": "Day"
},
"minItems": 1,
"maxItems": 14,
"title": "Days",
"description": "One to fourteen days in the order they are shown, each with its hours on a line of a timetable. Where the frame is short of height they turn pages."
},
"notice": {
"type": "string",
"maxLength": 140,
"title": "Notice",
"description": "A line after the timetable, in a quiet framed note, such as what stays open while the branch is closed."
},
"cta": {
"type": "object",
"properties": {
"label": {
"type": "string",
"maxLength": 28,
"title": "Button",
"description": "What the button says."
},
"detail": {
"type": "string",
"maxLength": 48,
"title": "Line under it",
"description": "An address, a phone number, a branch."
}
},
"required": [
"label"
],
"additionalProperties": false,
"title": "Call to action",
"description": "The button at the end and the line under it, such as a web address or a phone number."
},
"legal": {
"type": "string",
"maxLength": 260,
"title": "Legal text",
"description": "What must be shown with the post, such as who supervises the bank and how deposits are protected. On screen from the first frame to the last."
},
"look": {
"type": "object",
"properties": {
"theme": {
"type": "string",
"enum": [
"brand",
"light",
"dark",
"bold"
],
"title": "Theme",
"description": "Brand: your brand's own colours and background. Light or dark: a page tinted with your brand colour. Bold: your brand colour as the page.",
"enumNames": [
"Brand colours",
"Light",
"Dark",
"Bold brand colour"
]
},
"pace": {
"type": "string",
"enum": [
"calm",
"standard",
"punchy"
],
"title": "Pace",
"description": "How things arrive: calm fades, a standard rhythm, or punchy slams and hard cuts.",
"enumNames": [
"Calm",
"Standard",
"Punchy"
]
},
"corners": {
"type": "string",
"enum": [
"square",
"soft",
"round"
],
"title": "Corners",
"description": "The shape of cards, pictures and buttons.",
"enumNames": [
"Square",
"Softly rounded",
"Round"
]
},
"align": {
"type": "string",
"enum": [
"start",
"centre"
],
"title": "Text alignment",
"description": "Where the text lines up.",
"enumNames": [
"At the start of the line",
"Centred"
]
},
"logo": {
"type": "string",
"enum": [
"auto",
"free"
],
"title": "Logo",
"description": "The style's own: the logo on a small plate so it reads on any page. As it is: the logo whole, with no plate, frame or crop, for a logo made to stand on the page, such as one with a transparent background. As it is, make sure your logo reads on the page of the theme you choose.",
"enumNames": [
"The style's own",
"As it is, no plate or crop"
]
},
"logoSize": {
"type": "string",
"enum": [
"auto",
"small",
"large"
],
"title": "Logo size",
"description": "Standard is the style's own size. Small draws the logo a fifth smaller, with its plate. Large draws it a quarter larger wherever the style has room for it, and at the standard size where it has none.",
"enumNames": [
"Standard",
"Small",
"Large"
]
}
},
"additionalProperties": false,
"title": "Look",
"description": "How the clip looks and moves. The same six switches in every format (theme, pace, corners, alignment, logo, logo size). Leave any out for the package's default: the brand's colours, calm, square corners, text at the start."
}
},
"required": [
"headline",
"days"
],
"additionalProperties": false,
"title": "Clip data",
"description": "Everything drawn in the clip: the section, the headline, the branch and its address, the timetable, the notice, the call to action and the legal text."
},
"metaData": {
"type": "object",
"properties": {
"title": {
"type": "string",
"maxLength": 100,
"title": "Title",
"description": "Used where the platform has a title of its own: Facebook Feed, LinkedIn, Pinterest and YouTube. Up to 100 characters, which every one of them takes."
},
"captionTiny": {
"type": "string",
"maxLength": 200,
"title": "Tiny description (Pinterest, Threads, X)",
"description": "Up to 200 characters. The brand's description, link and hashtags follow it within the platform's limit."
},
"captionShort": {
"type": "string",
"maxLength": 500,
"title": "Short description (Telegram, Threema, WeChat, WhatsApp)",
"description": "Up to 500 characters. The brand's description, link and hashtags follow it within the platform's limit."
},
"captionMedium": {
"type": "string",
"maxLength": 1500,
"title": "Medium description (Discord, Facebook Reels, Instagram, LinkedIn, Signal, Slack, TikTok)",
"description": "Up to 1500 characters. The brand's description, link and hashtags follow it within the platform's limit."
},
"captionLong": {
"type": "string",
"maxLength": 4000,
"title": "Long description (Facebook Feed, YouTube)",
"description": "Up to 4000 characters. The brand's description, link and hashtags follow it within the platform's limit."
},
"hashtags": {
"type": "array",
"items": {
"type": "string",
"maxLength": 60,
"title": "Hashtag"
},
"maxItems": 15,
"title": "Hashtags",
"description": "Written without the #. Up to 15, which every platform takes."
}
},
"additionalProperties": false,
"title": "Meta data",
"description": "Everything about the post that is never drawn in the clip: its title, its description in four lengths, one per kind of platform, and its hashtags. Reelwire adds the brand's description, link and hashtags after it. Left out, the post text is generated from the clip data."
},
"locales": {
"type": "object",
"additionalProperties": {
"type": "object",
"properties": {
"clipData": {
"type": "object",
"properties": {
"section": {
"type": "string",
"maxLength": 24,
"title": "Section",
"description": "What the post is about, set small in capitals at the end of the header, such as Branches or Opening hours."
},
"headline": {
"type": "string",
"maxLength": 56,
"title": "Headline",
"description": "The title of the post."
},
"branch": {
"type": "string",
"maxLength": 48,
"title": "Branch",
"description": "The branch the hours are for, beside a place mark under the headline. Leave out for every branch."
},
"address": {
"type": "string",
"maxLength": 80,
"title": "Address",
"description": "The branch's address, under its name."
},
"days": {
"type": "array",
"items": {
"type": "object",
"properties": {
"day": {
"type": "string",
"maxLength": 24,
"title": "Day",
"description": "The day as it should be shown, such as Mon 22 Dec or Saturdays."
},
"hours": {
"type": "string",
"maxLength": 24,
"title": "Hours",
"description": "The hours as they should be shown, such as 9:00 to 16:00, or Closed."
},
"closed": {
"type": "boolean",
"title": "Closed",
"description": "The branch is closed that day: the line is drawn quieter."
},
"note": {
"type": "string",
"maxLength": 40,
"title": "Note",
"description": "A short word on the day, such as Closes early, marked in your brand colour."
}
},
"required": [
"day",
"hours"
],
"additionalProperties": false,
"title": "Day"
},
"minItems": 1,
"maxItems": 14,
"title": "Days",
"description": "One to fourteen days in the order they are shown, each with its hours on a line of a timetable. Where the frame is short of height they turn pages."
},
"notice": {
"type": "string",
"maxLength": 140,
"title": "Notice",
"description": "A line after the timetable, in a quiet framed note, such as what stays open while the branch is closed."
},
"cta": {
"type": "object",
"properties": {
"label": {
"type": "string",
"maxLength": 28,
"title": "Button",
"description": "What the button says."
},
"detail": {
"type": "string",
"maxLength": 48,
"title": "Line under it",
"description": "An address, a phone number, a branch."
}
},
"additionalProperties": false,
"title": "Call to action",
"description": "The button at the end and the line under it, such as a web address or a phone number."
},
"legal": {
"type": "string",
"maxLength": 260,
"title": "Legal text",
"description": "What must be shown with the post, such as who supervises the bank and how deposits are protected. On screen from the first frame to the last."
},
"look": {
"type": "object",
"properties": {
"theme": {
"type": "string",
"enum": [
"brand",
"light",
"dark",
"bold"
],
"title": "Theme",
"description": "Brand: your brand's own colours and background. Light or dark: a page tinted with your brand colour. Bold: your brand colour as the page.",
"enumNames": [
"Brand colours",
"Light",
"Dark",
"Bold brand colour"
]
},
"pace": {
"type": "string",
"enum": [
"calm",
"standard",
"punchy"
],
"title": "Pace",
"description": "How things arrive: calm fades, a standard rhythm, or punchy slams and hard cuts.",
"enumNames": [
"Calm",
"Standard",
"Punchy"
]
},
"corners": {
"type": "string",
"enum": [
"square",
"soft",
"round"
],
"title": "Corners",
"description": "The shape of cards, pictures and buttons.",
"enumNames": [
"Square",
"Softly rounded",
"Round"
]
},
"align": {
"type": "string",
"enum": [
"start",
"centre"
],
"title": "Text alignment",
"description": "Where the text lines up.",
"enumNames": [
"At the start of the line",
"Centred"
]
},
"logo": {
"type": "string",
"enum": [
"auto",
"free"
],
"title": "Logo",
"description": "The style's own: the logo on a small plate so it reads on any page. As it is: the logo whole, with no plate, frame or crop, for a logo made to stand on the page, such as one with a transparent background. As it is, make sure your logo reads on the page of the theme you choose.",
"enumNames": [
"The style's own",
"As it is, no plate or crop"
]
},
"logoSize": {
"type": "string",
"enum": [
"auto",
"small",
"large"
],
"title": "Logo size",
"description": "Standard is the style's own size. Small draws the logo a fifth smaller, with its plate. Large draws it a quarter larger wherever the style has room for it, and at the standard size where it has none.",
"enumNames": [
"Standard",
"Small",
"Large"
]
}
},
"additionalProperties": false,
"title": "Look",
"description": "How the clip looks and moves. The same six switches in every format (theme, pace, corners, alignment, logo, logo size). Leave any out for the package's default: the brand's colours, calm, square corners, text at the start."
}
},
"additionalProperties": false,
"title": "Clip data",
"description": "Everything drawn in the clip: the section, the headline, the branch and its address, the timetable, the notice, the call to action and the legal text."
},
"metaData": {
"type": "object",
"properties": {
"title": {
"type": "string",
"maxLength": 100,
"title": "Title",
"description": "Used where the platform has a title of its own: Facebook Feed, LinkedIn, Pinterest and YouTube. Up to 100 characters, which every one of them takes."
},
"captionTiny": {
"type": "string",
"maxLength": 200,
"title": "Tiny description (Pinterest, Threads, X)",
"description": "Up to 200 characters. The brand's description, link and hashtags follow it within the platform's limit."
},
"captionShort": {
"type": "string",
"maxLength": 500,
"title": "Short description (Telegram, Threema, WeChat, WhatsApp)",
"description": "Up to 500 characters. The brand's description, link and hashtags follow it within the platform's limit."
},
"captionMedium": {
"type": "string",
"maxLength": 1500,
"title": "Medium description (Discord, Facebook Reels, Instagram, LinkedIn, Signal, Slack, TikTok)",
"description": "Up to 1500 characters. The brand's description, link and hashtags follow it within the platform's limit."
},
"captionLong": {
"type": "string",
"maxLength": 4000,
"title": "Long description (Facebook Feed, YouTube)",
"description": "Up to 4000 characters. The brand's description, link and hashtags follow it within the platform's limit."
},
"hashtags": {
"type": "array",
"items": {
"type": "string",
"maxLength": 60,
"title": "Hashtag"
},
"maxItems": 15,
"title": "Hashtags",
"description": "Written without the #. Up to 15, which every platform takes."
}
},
"additionalProperties": false,
"title": "Meta data",
"description": "Everything about the post that is never drawn in the clip: its title, its description in four lengths, one per kind of platform, and its hashtags. Reelwire adds the brand's description, link and hashtags after it. Left out, the post text is generated from the clip data."
}
},
"additionalProperties": false
},
"propertyNames": {
"enum": [
"aa",
"ab",
"ae",
"af",
"ak",
"am",
"an",
"ar",
"as",
"av",
"ay",
"az",
"ba",
"be",
"bg",
"bi",
"bm",
"bn",
"bo",
"br",
"bs",
"ca",
"ce",
"ch",
"co",
"cr",
"cs",
"cu",
"cv",
"cy",
"da",
"de",
"dv",
"dz",
"ee",
"el",
"en",
"eo",
"es",
"et",
"eu",
"fa",
"ff",
"fi",
"fj",
"fo",
"fr",
"fy",
"ga",
"gd",
"gl",
"gn",
"gu",
"gv",
"ha",
"he",
"hi",
"ho",
"hr",
"ht",
"hu",
"hy",
"hz",
"ia",
"id",
"ie",
"ig",
"ii",
"ik",
"io",
"is",
"it",
"iu",
"ja",
"jv",
"ka",
"kg",
"ki",
"kj",
"kk",
"kl",
"km",
"kn",
"ko",
"kr",
"ks",
"ku",
"kv",
"kw",
"ky",
"la",
"lb",
"lg",
"li",
"ln",
"lo",
"lt",
"lu",
"lv",
"mg",
"mh",
"mi",
"mk",
"ml",
"mn",
"mr",
"ms",
"mt",
"my",
"na",
"nb",
"nd",
"ne",
"ng",
"nl",
"nn",
"no",
"nr",
"nv",
"ny",
"oc",
"oj",
"om",
"or",
"os",
"pa",
"pi",
"pl",
"ps",
"pt",
"qu",
"rm",
"rn",
"ro",
"ru",
"rw",
"sa",
"sc",
"sd",
"se",
"sg",
"si",
"sk",
"sl",
"sm",
"sn",
"so",
"sq",
"sr",
"ss",
"st",
"su",
"sv",
"sw",
"ta",
"te",
"tg",
"th",
"ti",
"tk",
"tl",
"tn",
"to",
"tr",
"ts",
"tt",
"tw",
"ty",
"ug",
"uk",
"ur",
"uz",
"ve",
"vi",
"vo",
"wa",
"wo",
"xh",
"yi",
"yo",
"za",
"zh",
"zu",
"zh-CN",
"zh-TW",
"pt-BR"
]
}
}
},
"required": [
"clipData"
],
"additionalProperties": false,
"$schema": "http://json-schema.org/draft-07/schema#"
}
Ask Reelwire for the same document yourself, without signing in:
curl "http://localhost:4000/v1/formats/template/tpl_c43b789e739a?variant=Opening%20hours&version=1.0.0"
Delivery JSON
Your own system sends this to one of your streams, POST /v1/stream, and every channel listening to
that stream draws it with its template. data is the document described above; a stream with an
empty field map passes it to the template unchanged. Try it first on POST /v1/stream/validate,
which answers the same without publishing anything. Streams and feeds
explains the rest, including how to map a document of your own shape instead.
{
"streamId": "st_your_stream_id",
"eventId": "01M2SQMKK3G1NVFXV31DCBVB95",
"occurredAt": "2026-09-23T07:00:00Z",
"locale": "en",
"data": {
"clipData": {
"section": "Branches",
"headline": "Our opening hours over the holidays",
"branch": "Zurich Paradeplatz",
"address": "Paradeplatz 8, 8001 Zurich",
"days": [
{
"day": "Mon 22 Dec",
"hours": "9:00 to 16:00"
},
{
"day": "Tue 23 Dec",
"hours": "9:00 to 16:00"
},
{
"day": "Wed 24 Dec",
"note": "Closes early",
"hours": "9:00 to 12:00"
},
{
"day": "Thu 25 Dec",
"hours": "Closed",
"closed": true
},
{
"day": "Fri 26 Dec",
"hours": "Closed",
"closed": true
},
{
"day": "Mon 29 Dec",
"hours": "9:00 to 16:00"
},
{
"day": "Wed 31 Dec",
"note": "Closes early",
"hours": "9:00 to 12:00"
},
{
"day": "Thu 1 Jan",
"hours": "Closed",
"closed": true
}
],
"notice": "Online and phone banking are available as usual, and our cash machines are open around the clock.",
"cta": {
"label": "Find your branch",
"detail": "alpstein.example/branches"
},
"legal": "Alpstein Privatbank AG is a licensed bank supervised by the financial market authority. Eligible deposits are protected up to 100,000 by the national deposit guarantee scheme.",
"look": {
"logo": "auto",
"pace": "calm",
"align": "start",
"theme": "brand",
"corners": "square",
"logoSize": "auto"
}
},
"metaData": {
"title": "Our opening hours over the holidays at Zurich Paradeplatz",
"captionTiny": "Our Zurich Paradeplatz branch over the holidays: closing at 12:00 on 24 and 31 December, closed on 25 and 26 December and 1 January.",
"captionShort": "Planning a visit to our Zurich Paradeplatz branch over the holidays? We are open 9:00 to 16:00 on 22, 23 and 29 December, and close early at 12:00 on 24 and 31 December. The branch is closed on 25 and 26 December and on 1 January. Online and phone banking are available as usual, and our cash machines are open around the clock.",
"captionMedium": "Planning a visit to our Zurich Paradeplatz branch over the holidays? Here are our opening hours. Monday 22 and Tuesday 23 December: 9:00 to 16:00. Wednesday 24 December: 9:00 to 12:00. Thursday 25 and Friday 26 December: closed. Monday 29 December: 9:00 to 16:00. Wednesday 31 December: 9:00 to 12:00. Thursday 1 January: closed. Online and phone banking are available as usual, and our cash machines are open around the clock. Opening hours for every branch are on our website. We wish you a peaceful holiday season.",
"captionLong": "Planning a visit to our Zurich Paradeplatz branch over the holidays? Here are our opening hours, so you can plan ahead. Monday 22 and Tuesday 23 December: 9:00 to 16:00. Wednesday 24 December: 9:00 to 12:00, as we close early on Christmas Eve. Thursday 25 and Friday 26 December: closed. Monday 29 December: 9:00 to 16:00. Wednesday 31 December: 9:00 to 12:00, as we close early on New Year's Eve. Thursday 1 January: closed. From Friday 2 January our usual hours apply again. While the branch is closed, online and phone banking are available as usual: you can make payments, check your accounts and reach our support team in the app or by phone. Our cash machines at Paradeplatz are open around the clock. Opening hours for every branch are on our website at alpstein.example/branches. Our whole team wishes you a peaceful holiday season and a good start to the new year.",
"hashtags": [
"openinghours",
"holidays",
"zurich",
"banking",
"branch"
]
}
}
}
The delivery names no version: which build draws it is the channel's choice, the newest of its
variant or the one it is pinned to. eventId is a ULID you make once per event: sending the same
one again changes nothing. occurredAt is when the thing happened, not when you sent it. locale
is the language of the text in data, and data.locales carries other languages beside it.