Answer "can GM Rewards / OEM rewards show on the customer screen or cashiering?" and CHECK whether the My GM Rewards 2.0 integration is enabled at a store by inspecting a live cashiering invoice. Covers the KB article set, where rewards display in the workflow, the manual-tender-vs-integrated-card tell, and how to request enablement. Verified at BC (1251) 2026-09-03.
Install
npx skillscat add joecastelino/jay-skill-pack/tekion-oem-rewards-integration Install via the SkillsCat registry.
Tekion My GM Rewards 2.0 / OEM Rewards Integration
The canonical KB articles (searched "GM Rewards" in the ServiceNow KB)
- KB0013265 — My GM Rewards 2.0 Updates (overview + prerequisites)
- KB0016771 — My GM Rewards 2.0 Integrations (WHERE it displays)
- KB0016773 — HOW TO: Redeem reward points for payments (cashiering steps + permissions)
- KB0016858 — Updates & Integrations video
- Related: KB0025690 (rewards refund), KB0025045 (cancel a rewards payment), KB0023596 (why can't redeem all points)
What the integration does (from the KBs)
When enabled, the customer's My GM Rewards card (Total Redeemable Dollars — NOT a raw
points count) displays across: Appointment Scheduling, Web/Mobile Check-In, Repair Order
(under Customer Information), Cashiering (below the other payment modes, with a Redeem
section: redemption amount auto-defaults to max + last-4-of-member-number verification),
PDF downloads (estimate/invoice show redeemable amount below total), and Customer
Management → OEM Rewards section. A yellow star icon next to the customer name marks
enrolled members throughout the service workflow.
Key facts:
- Requires customer email verification first — no verify, no card data.
- Rewards cover labor/parts/accessories only — never taxes or fees.
- Redemption void only until midnight of payment date.
- If the store is program-INELIGIBLE, the card still displays but Redeem is hidden.
- SALES ORDERS (counter sales) are NOT in the documented integration surface — every
redemption reference is RO-scoped. Don't claim SO support; refer to PSM. - Permissions needed: Cashiering Repair Order View + Pay (Roles → Permissions → Service → Cashiering).
Enablement is Tekion-side only
Not a dealer-flippable setting. Email support@tekion.com or the PSM requesting
"My GM Rewards 2.0 integration" for the store (give the full dealer string, e.g.
americanmotorscorporation_1251).
⚠️ BC ENABLEMENT STATUS — CORRECTED (Joe, 2026-09-03)
My GM Rewards 2.0 IS ENABLED at BC (1251). My earlier "not enabled" verdict was WRONG — two bad tests:
(1) checked the OEM Reward tender on a FLEET invoice (EXECUTIVE AUTO); (2) re-checked on RO 102371
(enrolled member Sylvester Smith) but BOTH payers were third-party (PORTFOLIO + External Service
Customer) — the rewards card only renders on the CUSTOMER's own pay section w/ verified email, so
neither test could show it. PROOF the integration is live = the Customer Management → OEM Rewards
tab fires the real-time gm-rewards/account-details call to GM and renders balances (verified).
LESSON: the manual "OEM Reward Details" tender form co-exists with 2.0 — its presence proves NOTHING
about enablement. To verify cashiering display, you need a pending CP invoice where the payer IS an
enrolled, email-verified member. Don't call "not enabled" off fleet/third-party-payer invoices.
How to CHECK if it's enabled (verified live, BC 2026-09-03)
Ground truth = a live cashiering invoice. Read-only, safe on production.
- Auth:
login.py(seetekion-autonomous-login). NOTE:--check/ALIVE can lie —
if injection renders the login form,login.py --force. Default dealer after
login = BC/1251. - Navigate
/ro/invoices(NOT/service/cashiering— blank shell; NOT/cashiering—
bounces to /home). Redirects to/ro/invoices/REPAIR_ORDER= the Cashiering list. - Open any Pending CP invoice: click the RO# cell (
.roid-wrapper.pointer) →/ro/invoices/REPAIR_ORDER/<objectId>. - The tell, in the payment section (Due Amount → Mode of Payment):
- NOT enabled: modes =
Credit/Debit Card | Cash | Check | OEM Reward | Other | Gift Card,
and clicking OEM Reward opens only blank manual fields (OEM Reward Details:
Amount*, ID, Transaction Date, Payment Notes). That's the generic manual tender —
cashier keys the amount by hand from the GM portal. - Enabled: a My GM Rewards card renders below the payment modes with the
customer's redeemable dollar balance + a Redeem section (amount defaults to max,
Last 4 Digits field). Grep innerText forGM Rewards|Redeem|Redemption|Last 4.
- NOT enabled: modes =
Script: /home/itadmin/tekion-reports/bc_gmrewards_check.py (standalone headless
Playwright + storage_state; adapt dealer check for other stores).
Pitfalls hit building this
- :9225/:9223 persistent instances flaked (SPA bounced to /home mid-click, then the
server endpoint itself HTTP 500'd). Perpersistent-browser-serverskill: on a 500,
do NOT retry — go straight to standalone headless Playwright with.tekion-storage-state.json. That worked first try. - Invoice detail innerText is only ~830 chars when fully rendered — small page, don't
poll waiting for >3000 chars like RO pages.Mode of Paymentpresent = loaded. - The "OEM Reward Details" form only appears AFTER clicking the OEM Reward mode (real
page.mouse.clickon the visible element center). - Discover left-rail app URLs generically:
[...document.querySelectorAll('a')]→ href map
(that's how/ro/invoiceswas found; C = Cashiering).
GM Rewards ENROLLMENT lookup per customer (cracked 2026-09-03 — works WITHOUT the 2.0 integration)
Customer Management stores rewards enrollment on the customer record even when the
cashiering integration is NOT enabled. Backing API (internal, replayable with captured
headers from /tmp/tekion_rec_headers.json + dealerId/tek-siteId swap — same auth set as
recommendation/search):
POST https://app.tekioncloud.com/api/lookup/search
{"CUSTOMER":{"filters":[{"field":"status","operator":"IN","values":["ACTIVE"]}],
"searchText":"<name or email>","pageInfo":{"start":0,"rows":3},"sort":[]}}Response: data.CUSTOMER.{count, entities[]} (NOT hits[]). Each entity's data node has:
customerRewardsInfos:[{"oem":"gm","redeemMyRewards":true,"rewardIds":[{"rewardId":"<email>","primary":true}]}]— WHEN populated = ENROLLED member. rewardId is usually the member's email.- ⚠️ NULL-FOR-MEMBERS TRAP (hit 2026-09-03 lapsed-customer scan):
customerRewardsInfosis
routinely null at BC even for CONFIRMED members (Aaron Pizarro = null stored, yet live GM
returns $425.90 Active). A 4,369-customer scan filtered on this field found ZERO members —
totally wrong. Membership ground truth = the livegm-rewards/account-detailscall keyed by
the customer's EMAIL (from lookupemailfield): 200 w/loyMember.memberNumber= member;
400 w/ errorCodeGM0007("member not found") = clean NOT-a-member signal. Treat GM0007 as
definitive no; only other HTTP errors are retryable. Also:POST /api/lookup/idsreturnsdata.CUSTOMERas a bare LIST of{id,displayValue,data}(NOT{entities:[]}like
lookup/search) — handle both shapes. Scan impl:~/tekion-reports/bc_gmrewards_lapsed_v2.py
(12mo RO index → last-visit map → lapsed filter → contacts via lookup/ids → per-email GM check,
all checkpointed). - Also present (usually null at BC):
oemRewardInfos,oemLoyaltyInfo{loyaltyNumber, fordCompCode, chryslerCompCode, volvoCompCode},loyaltyInfoByOEMs. - The stored record is ENROLLMENT only — but see the LIVE BALANCE endpoint below (Joe corrected me on this 2026-09-03: the Customer Management → OEM Rewards tab DOES show $ even without the 2.0 cashiering integration).
LIVE GM Rewards BALANCE lookup (cracked 2026-09-03 — Customer Mgmt OEM Rewards tab)
The OEM Rewards tab fires a real-time call to GM (takes ~2-4s — it's GM's loyalty system, not Tekion storage):
POST https://app.tekioncloud.com/api/service-module/u/gm-rewards/account-details
{"transactionDate":"MM/DD/YYYY","emailAddress":"<rewardId email>","memberNumber":""}Same captured-header auth set (any /api/ headers w/ tekion-api-token etc.; dealerId/tek-siteId swap per store).
Response data:
redemptionInformation."LOY Member":"Total Dollars","Total Points"(comma-formatted strings), Tier,"Dealer Eligible For Redeem".accountInformation.loyMember:totalDollars,totalPoints,status("Active"),canRedeem,memberNumber,memberNumberMasked, currentTier, spendTrackers.memberInformation: name/address, full member number.
Pace ~0.6s/call; 44 lookups ≈ 2.5 min. Keyed by the rewardId EMAIL from customerRewardsInfos.
DISCOVERY PITFALLS: customer detail page is NOT directly routable (/core/customer/detail/<id>renders empty)
— must go list (/core/customer) → in-page search input (placeholder "Search...", y>100, NOT the global
"Search here..." top bar) → type + ENTER (no Enter = no filter) → click result row (y>300) → click "OEM Rewards"
left-rail tab. Probe script:~/tekion-reports/bc_oemrewards_via_list.py(XHR+fetch hook via add_init_script).
BC combo result 2026-09-03: 44 members / $3,335.54 live rewards $ vs $61.7K deferred.
Output:~/tekion-reports/data/BC-GMRewards-members-declined-30d-with-balances.csv.- Batch fetch by id:
POST /api/lookup/ids{"CUSTOMER":{"ids":[...]}}same shape. - Pace ~0.25s; 360 lookups ≈ 2.5 min. Search by email first, fall back to name.
Proven combo report (2026-09-03): declined services 30d (tekion-declined-deferred-services-report)
→ dedupe to customers → lookup/search each → filter customerRewardsInfos populated.
BC result: 360 declined-svc customers → 44 GM Rewards members / 79 lines / $61.7K deferred.
Output: ~/tekion-reports/data/BC-GMRewards-members-declined-30d.csv. Probe script (browser
XHR-hook discovery path): ~/tekion-reports/bc_custmgmt_rewards_probe.py.
LAPSED-MEMBERS report ("Rewards members not in for 6-12 months") — 2026-09-03
Script: ~/tekion-reports/bc_gmrewards_lapsed_v2.py (adapt window/store; supersedes
bc_gmrewards_lapsed_6to12.py whose phase-2 enrollment filter was WRONG — see trap above). 3 phases:
- Last-visit map: enumerate 12 months of CLOSED/INVOICED ROs via OpenAPI
repair-orders:searchin 30-daycreationTime BTWwindows (paginationToken chaining is
SAFE for creationTime — the bisection bug is closedTime-only). The customer id is FREE on
each result:primaryCustomer.id(a{link,id}stub — no fan-out needed). Reduce to
{customerId: (max creationTime, RO#)}; checkpoint the RO index per window
(data/bc-lapsed-ro-index.json) so 429s resume. - Contacts IN BATCH:
POST /api/lookup/ids{"CUSTOMER":{"ids":[<=50 ids]}}
(captured-header auth, dealerid/tek-siteid swap) — 50 customers per call; pull name,
phone, EMAIL. ⚠️ Do NOT filter oncustomerRewardsInfoshere (null-for-members trap
above → zero hits). Responsedata.CUSTOMERis a bare LIST here. - Membership + live balance in ONE call: gm-rewards/account-details per UNIQUE email
(pace 0.6s; dedupe emails first — 4,368 contacts → 2,805 unique). 200 = member w/
$/points/tier; 400 GM0007 = definitively not a member.
Lapsed filter =t12 <= lastVisit < t6. Sort output by live Rewards $ (the callback
priority). Phase 1 dominates alongside phase 3 (~25K ROs ≈ 500 search pages; 2,805 GM
checks ≈ 30 min) — run background w/ notify_on_complete, never execute_code.
BC result 2026-09-04: 11,834 customers/12mo → 4,368 lapsed → 1,456 GM members (52% of
checked emails!) / $138,807.68 unredeemed / 643 holding $50+. Membership is common —
never assume it's rare. Outputdata/BC-GMRewards-lapsed-6to12mo.csv; emailed via
jay_mail.py send_report (top-50 table in body + full CSV attached).
Cross-references
- OBSOLETE claim (pre-2026-09-03): "rewards-members-who-did-X reports are impossible
pre-enablement" — WRONG. The live account-details endpoint answers membership + balance
for ANY email regardless of cashiering-integration status. Both combo reports (declined-30d,
lapsed-6-12mo) shipped to Joe. tekion-declined-deferred-services-report(the declined-customer list half of that ask),tekion-sitemap(Cashiering row),tekion-autonomous-login,persistent-browser-server,tekion-kb-search-scrape(KB SSO bootstrap: navigateapp.tekioncloud.com/core/knowledge-base/search).