Event Connectors

Promotion

Promotional offers attached to events: types, discount shapes, validity strategies, per-day-of-week availability, and external references

Validate Promotion objects at runtime with PromotionSchema from @eventconnectors/ndtrc_model.

A Promotion is a special offer attached to a TRCItem (typically an Event). It describes what the offer is (free entry, percentage off, gift), when it's valid (always, in a date range, only as an early-bird, only as a last-minute), and how often it's available (always, or only on specific weekdays).

A single TRCItem may carry several promotions in parallel — e.g. an early-bird discount up to two weeks before, plus a last-minute discount in the final 24 hours, plus a "groups of 10+" gift offer.

Promotion shape

The full set of fields:

FieldTypeDescription
productstringInternal label for the offer (e.g. "early-bird", "citycard", "1one1"). Human-readable identifier; not shown directly to consumers.
externalReferencestringOptional pointer to the same offer in your own system. Free-form. Max 255 characters.
promotionTypeenumnone, free, discount, gift, allowance — see PromotionType.
discountobjectShape of the price reduction. See Discount.
discountValueRequiredbooleanWhether the promotion has to carry a discount value to count as complete. Defaults to true. See Discount value required.
imageobjectOptional File — usually the logo of the offer — shown next to the promotion. See Image.
enabledbooleanMaster on/off switch. Defaults to true. Setting to false disables the promotion regardless of validity strategy.
restrictedToRegisteredUsersbooleanWhen true, the promotion is only available to logged-in users on partner sites.
validityStrategyenumalways, dateRange, earlyBird, lastMinute — see ValidityStrategy.
startDatedateStart of the validity window (used by dateRange).
endDatedateEnd of the validity window (used by dateRange).
eventRelativeDurationstringISO 8601 duration relative to the event's start. Used by earlyBird and lastMinute. See Event-relative durations.
opensarrayOptional per-day-of-week or per-month restriction. Same Open shape as in Calendar.
translationsarray{ lang, description } entries. The description supports simple HTML (<a>, <b>, <i>).
detailsUrlsarrayOptional list of Url objects (see Contactinfo) pointing at terms, the booking page, partner offer details, etc.

PromotionType

ValueMeaning
noneTracking-only entry — the promotion exists for record-keeping (e.g. an externalReference to a partner system) but represents no active offer. Useful for stitching FeedFactory promotions to upstream systems without implying a discount.
freeFree entry or free item. Pair with discount.free = true.
discountPrice reduction. Use discount.percentage (0–100) or discount.amount (absolute, EUR).
giftFree gift bundled with the regular ticket. Doesn't reduce the ticket price.
allowanceSpending allowance or voucher (e.g. "€10 to spend at the bar").

Discount

The discount object has three optional fields and the relevant ones are populated based on promotionType:

FieldTypeUsed when
freebooleanpromotionType: "free" — set to true
percentageintegerpromotionType: "discount" — percentage off (0–100)
amountnumberpromotionType: "discount" — absolute discount in EUR

For percentage vs amount: pick one. If both are set, partner consumers may render either; behavior is unspecified.

Discount value required

discountValueRequired says whether this promotion has to carry a concrete value — a discount.percentage or a discount.amount — to count as complete. It exists because the completeness check on an event or a location also looks at the promotions attached to it:

ValueCompleteness check
true (default)A promotion with neither discount.percentage nor discount.amount makes the event or location incomplete, so the missing value is visible instead of silently absent.
falseThe promotion is exempt — it needs no value. Use it for offers that have none: a gift, a present, free entrance, a tracking-only entry.

It is set on the account-level promotion product (Account → Promotions in the console), so an admin answers the question once per offer. An item picks the value up when the promotion is added to it. Promotions already on an item keep their own copy — the automatic fan-out re-syncs only enabled — so changing the product afterwards does not rewrite items that already carry it, exactly as for discount and translations. Set it on the item itself to correct one of those.

discountValueRequired is deliberately independent of promotionType. The type says what kind of offer it is; this says whether a value is expected, and the two are not the same question — a discount whose price is settled in a partner system carries no percentage here, while an allowance of "€10 at the bar" does carry an amount. Read the boolean; don't infer it from the type.

It is always present on a response. A promotion created before the field existed reads back as true, so nothing is silently exempted.

{
  "product": "tuesday-gift",
  "promotionType": "gift",
  "discountValueRequired": false,
  "translations": [{ "lang": "nl", "description": "Op dinsdag een gratis drankje" }]
}

Image

image holds an optional File for the promotion — normally the logo of the offer, such as a city card or a partner brand. It is what the console renders next to the promotion, and it is returned on read so publishing sites can render the same logo.

It is one File, not an array, and it uses the same shape as the rest of the model, so it carries hlink, copyright, and per-language title translations. mediatype: "logo" is the natural choice for a brand mark. The promotion's image is unrelated to the TRCItem's own files — those are the photos of the event itself.

{
  "product": "citycard",
  "promotionType": "discount",
  "discount": { "percentage": 25 },
  "image": {
    "hlink": "https://cdn.example.com/citycard-logo.png",
    "filename": "citycard-logo.png",
    "filetype": "png",
    "mediatype": "logo",
    "copyright": "City Card",
    "title": {
      "label": "City Card",
      "titleTranslations": [{ "lang": "nl", "label": "City Card" }]
    }
  }
}

ValidityStrategy

Controls when the promotion is active:

ValueActive whenRequired fields
always (default)Whenever enabled = true—
dateRangeBetween startDate and endDatestartDate, endDate
earlyBirdMore than eventRelativeDuration before the event starteventRelativeDuration
lastMinuteWithin eventRelativeDuration of the event starteventRelativeDuration

opens adds a second filter on top of validityStrategy: even if the strategy says "currently active", the promotion is only honoured when today matches one of the opens entries. Useful for offers like "Tuesdays only" or "every first Monday of the month".

Event-relative durations

eventRelativeDuration uses the ISO 8601 duration format.

DurationMeaning
PT1H1 hour
PT24H / P1D24 hours / 1 day
P3D3 days
P2W2 weeks
P1M1 month
P3Y6M4DT12H30M5S3 years, 6 months, 4 days, 12 hours, 30 minutes, 5 seconds

For earlyBird, the duration is read as "the offer is valid up until this much before the event starts". P2W means the offer ends 2 weeks before showtime. For lastMinute, the duration is "the offer kicks in this much before the event starts" — PT24H opens the deal in the final day.

Examples

Free entry on the day of the event

{
  "product": "free-day-pass",
  "promotionType": "free",
  "validityStrategy": "lastMinute",
  "eventRelativeDuration": "PT24H",
  "discount": { "free": true },
  "translations": [
    { "lang": "nl", "description": "Gratis toegang op de dag zelf" },
    { "lang": "en", "description": "Free entry on the day itself" }
  ]
}

Early-bird 20% discount until two weeks before

{
  "product": "early-bird-2025",
  "promotionType": "discount",
  "validityStrategy": "earlyBird",
  "eventRelativeDuration": "P2W",
  "discount": { "percentage": 20 },
  "translations": [
    { "lang": "nl", "description": "20% korting bij vroeg boeken" },
    { "lang": "en", "description": "20% off when booking early" }
  ]
}

Fixed €5 discount during a date range

{
  "product": "summer-sale",
  "promotionType": "discount",
  "validityStrategy": "dateRange",
  "startDate": "2026-06-01",
  "endDate": "2026-08-31",
  "discount": { "amount": 5.0 },
  "translations": [
    { "lang": "nl", "description": "Zomerkorting: €5 voordeel" },
    { "lang": "en", "description": "Summer sale: €5 off" }
  ]
}

Tuesday-only gift, registered users only

{
  "product": "tuesday-gift",
  "promotionType": "gift",
  "discountValueRequired": false,
  "enabled": true,
  "restrictedToRegisteredUsers": true,
  "validityStrategy": "always",
  "opens": [
    { "day": 3, "whens": [{ "timestart": "00:00", "timeend": "23:59" }] }
  ],
  "translations": [
    { "lang": "nl", "description": "Op dinsdag een gratis drankje (alleen voor leden)" },
    { "lang": "en", "description": "Free drink on Tuesdays (members only)" }
  ]
}

The opens entry uses the same day-of-week numbering as the rest of the API: 1=Sunday, 2=Monday, 3=Tuesday, 4=Wednesday, 5=Thursday, 6=Friday, 7=Saturday.

Partner-system promotion with no FeedFactory-side offer

{
  "product": "partner-citycard",
  "externalReference": "CITYCARD-CAMPAIGN-2026-Q1",
  "promotionType": "none",
  "discountValueRequired": false,
  "enabled": true,
  "validityStrategy": "always"
}

promotionType: "none" is the right choice when the entry exists only to point at a partner system (via externalReference) — there's no FeedFactory-side discount to render. An entry like this carries no value of its own, so set discountValueRequired: false on it as well; otherwise every event carrying it is reported as missing a discount value. Decide that per promotion, though — it follows from this entry having no offer, not from its promotionType.

Per-event vs. account-level templates

Promotions on a TRCItem are instance-level — they apply to that single event/location/venue/route. There are also account-level promotion templates (configured under Account → Promotions in the console) that fan out automatically across an account's events, locations, or venues based on matching rules.

The externalReference field is not propagated by template-based cloning: clones start with externalReference = null. If you need an external pointer, set it on the per-event promotion after the clone.

External reference

externalReference is a free-form string (max 255 characters) that connects this promotion to its counterpart in another system — a partner site's "actie" record, a CRM coupon code, a ticketing-platform promotion ID. Event Connectors does not validate or interpret the value; it round-trips as-is on every read and write.

Use it to avoid inventing marker conventions or out-of-band spreadsheets to track which FeedFactory promotion corresponds to which partner record.

Translations

Each promotion carries a translations array of { lang, description } objects. The description is the user-facing text consumers render (in the matching language) — it supports simple HTML elements like <a>, <b>, <i>. Plain \n is not rendered as a line break; wrap content in HTML if you need formatting.

{
  "translations": [
    { "lang": "nl", "description": "20% korting <b>tot 1 mei</b>" },
    { "lang": "en", "description": "20% off <b>until 1 May</b>" }
  ]
}

Details URLs

Optional links to more information about the promotion — terms, booking flows, partner offer pages. Each entry is a Url with an optional urlServiceType (general, booking, webshop, etc.) and per-language description translations.

{
  "detailsUrls": [
    {
      "url": "https://partner.example.com/offers/early-bird",
      "urlServiceType": "booking",
      "descriptionTranslations": [
        { "lang": "nl", "label": "Boek met early-bird tarief" },
        { "lang": "en", "label": "Book at early-bird rate" }
      ]
    }
  ]
}

Notes

  • The product field is not shown directly to consumers — it's an internal identifier. Render translations[lang].description instead.
  • An enabled: false promotion is suppressed everywhere, even if its validityStrategy would otherwise mark it active. Use this as a quick kill-switch.
  • A promotion with no validityStrategy defaults to always.
  • A promotion with validityStrategy: "dateRange" but missing startDate or endDate is treated as inactive.
  • discountValueRequired affects the completeness check only — it never suppresses the promotion the way enabled: false does.

See the API Reference for the full Promotion schema.

On this page