@proveanything/smartlinks 1.13.8 → 1.13.10
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/docs/API_SUMMARY.md +116 -17
- package/dist/docs/interactions.md +200 -0
- package/dist/openapi.yaml +181 -3
- package/dist/types/interaction.d.ts +97 -0
- package/dist/types/product.d.ts +12 -3
- package/dist/utils/conditions.d.ts +4 -11
- package/dist/utils/conditions.js +3 -4
- package/docs/API_SUMMARY.md +116 -17
- package/docs/interactions.md +200 -0
- package/openapi.yaml +181 -3
- package/package.json +1 -1
package/dist/docs/API_SUMMARY.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Smartlinks API Summary
|
|
2
2
|
|
|
3
|
-
Version: 1.13.
|
|
3
|
+
Version: 1.13.10 | Generated: 2026-05-12T16:12:33.784Z
|
|
4
4
|
|
|
5
5
|
This is a concise summary of all available API functions and types.
|
|
6
6
|
|
|
@@ -5457,8 +5457,9 @@ interface InteractionTypeRecord {
|
|
|
5457
5457
|
permissions?: InteractionPermissions
|
|
5458
5458
|
data?: {
|
|
5459
5459
|
display?: InteractionDisplay
|
|
5460
|
-
scopes?: Record<string, InteractionDisplay
|
|
5460
|
+
scopes?: Record<string, InteractionDisplay>
|
|
5461
5461
|
interactionType?: string
|
|
5462
|
+
effects?: InteractionEffect[]
|
|
5462
5463
|
[key: string]: unknown
|
|
5463
5464
|
}
|
|
5464
5465
|
createdAt: string
|
|
@@ -5526,6 +5527,113 @@ interface SubmitInteractionError {
|
|
|
5526
5527
|
}
|
|
5527
5528
|
```
|
|
5528
5529
|
|
|
5530
|
+
**InteractionEffect** (interface)
|
|
5531
|
+
```typescript
|
|
5532
|
+
interface InteractionEffect {
|
|
5533
|
+
type: EffectType
|
|
5534
|
+
config?: EffectConfig
|
|
5535
|
+
}
|
|
5536
|
+
```
|
|
5537
|
+
|
|
5538
|
+
**LoyaltyEffectConfig** (interface)
|
|
5539
|
+
```typescript
|
|
5540
|
+
interface LoyaltyEffectConfig {
|
|
5541
|
+
[key: string]: never
|
|
5542
|
+
}
|
|
5543
|
+
```
|
|
5544
|
+
|
|
5545
|
+
**TransactionalEffectConfig** (interface)
|
|
5546
|
+
```typescript
|
|
5547
|
+
interface TransactionalEffectConfig {
|
|
5548
|
+
templateId: string
|
|
5549
|
+
* Channel to use.
|
|
5550
|
+
* Default: 'preferred' — auto-selects the contact's best available channel.
|
|
5551
|
+
channel?: 'email' | 'sms' | 'push' | 'whatsapp' | 'wallet' | 'preferred'
|
|
5552
|
+
props?: Record<string, unknown>
|
|
5553
|
+
include?: {
|
|
5554
|
+
productId?: string
|
|
5555
|
+
proofId?: string
|
|
5556
|
+
user?: boolean
|
|
5557
|
+
appCase?: string
|
|
5558
|
+
appThread?: string
|
|
5559
|
+
appRecord?: string
|
|
5560
|
+
}
|
|
5561
|
+
appId?: string
|
|
5562
|
+
}
|
|
5563
|
+
```
|
|
5564
|
+
|
|
5565
|
+
**WebhookEffectConfig** (interface)
|
|
5566
|
+
```typescript
|
|
5567
|
+
interface WebhookEffectConfig {
|
|
5568
|
+
url: string
|
|
5569
|
+
method?: 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE'
|
|
5570
|
+
headers?: Record<string, string>
|
|
5571
|
+
body?: Record<string, unknown>
|
|
5572
|
+
timeout?: number
|
|
5573
|
+
}
|
|
5574
|
+
```
|
|
5575
|
+
|
|
5576
|
+
**TagEffectConfig** (interface)
|
|
5577
|
+
```typescript
|
|
5578
|
+
interface TagEffectConfig {
|
|
5579
|
+
tags: string[]
|
|
5580
|
+
action?: 'add' | 'remove'
|
|
5581
|
+
}
|
|
5582
|
+
```
|
|
5583
|
+
|
|
5584
|
+
**AppRecordEffectConfig** (interface)
|
|
5585
|
+
```typescript
|
|
5586
|
+
interface AppRecordEffectConfig {
|
|
5587
|
+
appId?: string
|
|
5588
|
+
recordType?: string
|
|
5589
|
+
* Singleton cardinality key. At most one record per recordType+singletonPer will
|
|
5590
|
+
* exist per scope. Common values: 'contact', 'product', 'proof', 'global'
|
|
5591
|
+
singletonPer?: string
|
|
5592
|
+
data?: Record<string, unknown>
|
|
5593
|
+
anchors?: {
|
|
5594
|
+
productId?: string
|
|
5595
|
+
proofId?: string
|
|
5596
|
+
variantId?: string
|
|
5597
|
+
batchId?: string
|
|
5598
|
+
}
|
|
5599
|
+
}
|
|
5600
|
+
```
|
|
5601
|
+
|
|
5602
|
+
**SegmentEffectConfig** (interface)
|
|
5603
|
+
```typescript
|
|
5604
|
+
interface SegmentEffectConfig {
|
|
5605
|
+
segmentId: string
|
|
5606
|
+
action?: 'add' | 'remove'
|
|
5607
|
+
}
|
|
5608
|
+
```
|
|
5609
|
+
|
|
5610
|
+
**InteractionEventContext** (interface)
|
|
5611
|
+
```typescript
|
|
5612
|
+
interface InteractionEventContext {
|
|
5613
|
+
eventId: string | null
|
|
5614
|
+
collectionId: string
|
|
5615
|
+
appId: string | null
|
|
5616
|
+
interactionId: string | null
|
|
5617
|
+
contactId: string | null
|
|
5618
|
+
userId: string | null
|
|
5619
|
+
productId: string | null
|
|
5620
|
+
proofId: string | null
|
|
5621
|
+
variantId: string | null
|
|
5622
|
+
batchId: string | null
|
|
5623
|
+
outcome: string | null
|
|
5624
|
+
eventType: string | null
|
|
5625
|
+
source: string | null
|
|
5626
|
+
timestamp: string | null
|
|
5627
|
+
metadata: Record<string, unknown>
|
|
5628
|
+
broadcastId: string | null
|
|
5629
|
+
journeyId: string | null
|
|
5630
|
+
}
|
|
5631
|
+
```
|
|
5632
|
+
|
|
5633
|
+
**EffectType** = ``
|
|
5634
|
+
|
|
5635
|
+
**EffectConfig** = ``
|
|
5636
|
+
|
|
5529
5637
|
### jobs
|
|
5530
5638
|
|
|
5531
5639
|
**Job** (interface)
|
|
@@ -6581,13 +6689,6 @@ interface ProductFacetValue {
|
|
|
6581
6689
|
}
|
|
6582
6690
|
```
|
|
6583
6691
|
|
|
6584
|
-
**ProductFacetMap** (interface)
|
|
6585
|
-
```typescript
|
|
6586
|
-
interface ProductFacetMap {
|
|
6587
|
-
[facetKey: string]: ProductFacetValue[]
|
|
6588
|
-
}
|
|
6589
|
-
```
|
|
6590
|
-
|
|
6591
6692
|
**ProductQueryRequest** (interface)
|
|
6592
6693
|
```typescript
|
|
6593
6694
|
interface ProductQueryRequest {
|
|
@@ -6674,6 +6775,8 @@ interface ProductQueryResponse {
|
|
|
6674
6775
|
|
|
6675
6776
|
**ISODateString** = `string`
|
|
6676
6777
|
|
|
6778
|
+
**ProductFacetMap** = `Record<string, string[]>`
|
|
6779
|
+
|
|
6677
6780
|
**ProductClaimCreateRequestBody** = `Omit<ProductClaimCreateInput, 'collectionId' | 'id'>`
|
|
6678
6781
|
|
|
6679
6782
|
**ProductResponse** = `Product`
|
|
@@ -7414,18 +7517,14 @@ interface UserInfo {
|
|
|
7414
7517
|
interface ProductInfo {
|
|
7415
7518
|
id: string
|
|
7416
7519
|
tags?: Record<string, any>
|
|
7417
|
-
* Facet
|
|
7418
|
-
*
|
|
7419
|
-
* Each value object must have at minimum a `key` string property.
|
|
7520
|
+
* Facet assignments on this product: maps each facet key to an array of assigned
|
|
7521
|
+
* value slugs/keys. Matches the slim shape returned by the Products API.
|
|
7420
7522
|
*
|
|
7421
7523
|
* @example
|
|
7422
7524
|
* ```ts
|
|
7423
|
-
* {
|
|
7424
|
-
* material: [{ key: 'cotton', name: 'Cotton' }],
|
|
7425
|
-
* certifications: [{ key: 'organic', name: 'Organic' }, { key: 'recycled', name: 'Recycled' }]
|
|
7426
|
-
* }
|
|
7525
|
+
* { material: ['cotton'], certifications: ['organic', 'recycled'] }
|
|
7427
7526
|
* ```
|
|
7428
|
-
facets?: Record<string,
|
|
7527
|
+
facets?: Record<string, string[]>
|
|
7429
7528
|
}
|
|
7430
7529
|
```
|
|
7431
7530
|
|
|
@@ -280,6 +280,195 @@ When defining a journey trigger, reference the `interactionId` that should fire
|
|
|
280
280
|
|
|
281
281
|
---
|
|
282
282
|
|
|
283
|
+
## Interaction Effects
|
|
284
|
+
|
|
285
|
+
**Interaction effects** are automatic side-effects that fire immediately after an interaction event is recorded. They are defined directly on the interaction definition (`data.effects`) and run inline, fire-and-forget — they never block or fail the interaction response.
|
|
286
|
+
|
|
287
|
+
Common use cases: send a confirmation email, post a webhook to a CRM, tag the contact, award loyalty points, create an app record, or add the contact to a segment.
|
|
288
|
+
|
|
289
|
+
### How It Works
|
|
290
|
+
|
|
291
|
+
1. Event is logged to BigQuery
|
|
292
|
+
2. Interaction definition is loaded
|
|
293
|
+
3. Each configured effect runs **in order**, fire-and-forget
|
|
294
|
+
4. Response is returned to the caller immediately
|
|
295
|
+
|
|
296
|
+
A failure in one effect is logged and swallowed; subsequent effects still run.
|
|
297
|
+
|
|
298
|
+
> **Note:** `transactional` and `webhook` effects currently run inline. They are planned to move to background jobs for retry-on-failure support once the jobs infrastructure is fully operational.
|
|
299
|
+
|
|
300
|
+
### Configuring Effects
|
|
301
|
+
|
|
302
|
+
Pass `data.effects` when creating or updating an interaction type:
|
|
303
|
+
|
|
304
|
+
```typescript
|
|
305
|
+
await SL.interactions.create(collectionId, {
|
|
306
|
+
id: 'donation',
|
|
307
|
+
appId: 'my-app',
|
|
308
|
+
permissions: { allowPublicSubmit: true },
|
|
309
|
+
data: {
|
|
310
|
+
effects: [
|
|
311
|
+
{
|
|
312
|
+
type: 'transactional',
|
|
313
|
+
config: {
|
|
314
|
+
templateId: 'tmpl_donation_receipt',
|
|
315
|
+
channel: 'email',
|
|
316
|
+
props: {
|
|
317
|
+
donationAmount: '{{metadata.amount}}',
|
|
318
|
+
campaignName: '{{metadata.campaign}}',
|
|
319
|
+
},
|
|
320
|
+
},
|
|
321
|
+
},
|
|
322
|
+
{
|
|
323
|
+
type: 'tag',
|
|
324
|
+
config: { tags: ['donor'] },
|
|
325
|
+
},
|
|
326
|
+
],
|
|
327
|
+
},
|
|
328
|
+
});
|
|
329
|
+
```
|
|
330
|
+
|
|
331
|
+
### Token Interpolation
|
|
332
|
+
|
|
333
|
+
All effect `config` values support `{{token}}` interpolation resolved from the event context at runtime. Nested paths using dot notation are supported (e.g. `{{metadata.amount}}`).
|
|
334
|
+
|
|
335
|
+
| Token | Description |
|
|
336
|
+
|---|---|
|
|
337
|
+
| `{{eventId}}` | BigQuery event UUID |
|
|
338
|
+
| `{{collectionId}}` | Collection / brand ID |
|
|
339
|
+
| `{{appId}}` | App ID that submitted the event |
|
|
340
|
+
| `{{interactionId}}` | Interaction definition ID |
|
|
341
|
+
| `{{contactId}}` | Contact UUID (if resolved) |
|
|
342
|
+
| `{{userId}}` | Firebase UID (if authenticated) |
|
|
343
|
+
| `{{productId}}` | Product ID (if provided with event) |
|
|
344
|
+
| `{{proofId}}` | Proof ID (if provided with event) |
|
|
345
|
+
| `{{variantId}}` | Variant ID (if provided with event) |
|
|
346
|
+
| `{{batchId}}` | Batch ID (if provided with event) |
|
|
347
|
+
| `{{outcome}}` | Event outcome (e.g. `"submitted"`) |
|
|
348
|
+
| `{{eventType}}` | Event type string |
|
|
349
|
+
| `{{source}}` | Event source string |
|
|
350
|
+
| `{{timestamp}}` | ISO 8601 event timestamp |
|
|
351
|
+
| `{{metadata.*}}` | Any key from the event `metadata` object |
|
|
352
|
+
|
|
353
|
+
Tokens that resolve to `null` or `undefined` become an empty string.
|
|
354
|
+
|
|
355
|
+
### Effect Types
|
|
356
|
+
|
|
357
|
+
#### `loyalty`
|
|
358
|
+
|
|
359
|
+
Awards loyalty points by evaluating `LoyaltyEarningRule` records configured against this interaction. Earning rules are managed separately via the Loyalty API. If no rules are configured, this effect is a no-op.
|
|
360
|
+
|
|
361
|
+
```json
|
|
362
|
+
{ "type": "loyalty", "config": {} }
|
|
363
|
+
```
|
|
364
|
+
|
|
365
|
+
#### `transactional`
|
|
366
|
+
|
|
367
|
+
Sends a message to the contact using a comms template. Supports all channels with full Liquid template hydration.
|
|
368
|
+
|
|
369
|
+
```json
|
|
370
|
+
{
|
|
371
|
+
"type": "transactional",
|
|
372
|
+
"config": {
|
|
373
|
+
"templateId": "tmpl_donation_receipt",
|
|
374
|
+
"channel": "email",
|
|
375
|
+
"props": {
|
|
376
|
+
"donationAmount": "{{metadata.amount}}"
|
|
377
|
+
}
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
```
|
|
381
|
+
|
|
382
|
+
`channel` defaults to `"preferred"` — auto-selects the contact's best available channel respecting consent, suppression, and template availability. Optionally pass `include` to hydrate product/proof/user context, and `props` for additional Liquid variables.
|
|
383
|
+
|
|
384
|
+
#### `webhook`
|
|
385
|
+
|
|
386
|
+
Posts a JSON payload to an HTTP endpoint. All config values support `{{token}}` interpolation.
|
|
387
|
+
|
|
388
|
+
```json
|
|
389
|
+
{
|
|
390
|
+
"type": "webhook",
|
|
391
|
+
"config": {
|
|
392
|
+
"url": "https://crm.example.com/api/events",
|
|
393
|
+
"headers": { "Authorization": "Bearer sk_live_abc123" },
|
|
394
|
+
"body": {
|
|
395
|
+
"contactId": "{{contactId}}",
|
|
396
|
+
"amount": "{{metadata.amount}}"
|
|
397
|
+
},
|
|
398
|
+
"timeout": 8000
|
|
399
|
+
}
|
|
400
|
+
}
|
|
401
|
+
```
|
|
402
|
+
|
|
403
|
+
When `body` is omitted, a standard payload is sent containing `eventId`, `collectionId`, `appId`, `interactionId`, `contactId`, `userId`, `productId`, `proofId`, `outcome`, `eventType`, `metadata`, and `timestamp`.
|
|
404
|
+
|
|
405
|
+
#### `tag`
|
|
406
|
+
|
|
407
|
+
Adds or removes string tags on the contact record. Skipped if there is no `contactId` on the event.
|
|
408
|
+
|
|
409
|
+
```json
|
|
410
|
+
{
|
|
411
|
+
"type": "tag",
|
|
412
|
+
"config": {
|
|
413
|
+
"tags": ["donor", "tier-{{metadata.tier}}"],
|
|
414
|
+
"action": "add"
|
|
415
|
+
}
|
|
416
|
+
}
|
|
417
|
+
```
|
|
418
|
+
|
|
419
|
+
`action` defaults to `"add"`. Duplicate adds are no-ops; removing a tag not present is a no-op.
|
|
420
|
+
|
|
421
|
+
#### `appRecord`
|
|
422
|
+
|
|
423
|
+
Creates or upserts an app record scoped to the event context. Use `singletonPer` for cardinality control — if a matching singleton already exists it is updated rather than duplicated.
|
|
424
|
+
|
|
425
|
+
```json
|
|
426
|
+
{
|
|
427
|
+
"type": "appRecord",
|
|
428
|
+
"config": {
|
|
429
|
+
"recordType": "participation",
|
|
430
|
+
"singletonPer": "contact",
|
|
431
|
+
"data": {
|
|
432
|
+
"participatedAt": "{{timestamp}}",
|
|
433
|
+
"outcome": "{{outcome}}"
|
|
434
|
+
}
|
|
435
|
+
}
|
|
436
|
+
}
|
|
437
|
+
```
|
|
438
|
+
|
|
439
|
+
Common `singletonPer` values: `"contact"`, `"product"`, `"proof"`, `"global"`.
|
|
440
|
+
|
|
441
|
+
#### `segment`
|
|
442
|
+
|
|
443
|
+
Adds or removes the contact from a **static** segment. Skipped if there is no `contactId` on the event.
|
|
444
|
+
|
|
445
|
+
```json
|
|
446
|
+
{
|
|
447
|
+
"type": "segment",
|
|
448
|
+
"config": {
|
|
449
|
+
"segmentId": "seg_donors_2026",
|
|
450
|
+
"action": "add"
|
|
451
|
+
}
|
|
452
|
+
}
|
|
453
|
+
```
|
|
454
|
+
|
|
455
|
+
The segment must already exist and be of `filterType: 'static'`.
|
|
456
|
+
|
|
457
|
+
### Effects Error Handling
|
|
458
|
+
|
|
459
|
+
| Scenario | Behaviour |
|
|
460
|
+
|---|---|
|
|
461
|
+
| Effect throws (e.g. template not found, webhook 500) | Error logged server-side; next effect continues |
|
|
462
|
+
| Contact has no email, `transactional` channel = `email` | Effect throws, logged, skipped |
|
|
463
|
+
| `contactId` absent on anonymous event | `tag`, `appRecord`, `segment` log a warning and skip |
|
|
464
|
+
| Unknown `type` value | Warning logged, effect skipped |
|
|
465
|
+
| `loyalty` — no earning rules configured | Silent no-op |
|
|
466
|
+
| Interaction has no `data.effects` | No effects run, no overhead |
|
|
467
|
+
|
|
468
|
+
Effects do **not** propagate errors back to the API caller.
|
|
469
|
+
|
|
470
|
+
---
|
|
471
|
+
|
|
283
472
|
## TypeScript Types
|
|
284
473
|
|
|
285
474
|
```typescript
|
|
@@ -299,6 +488,17 @@ import type {
|
|
|
299
488
|
AdminInteractionsCountsByOutcomeRequest,
|
|
300
489
|
PublicInteractionsCountsByOutcomeRequest,
|
|
301
490
|
PublicInteractionsByUserRequest,
|
|
491
|
+
// Effects
|
|
492
|
+
InteractionEffect, // Single effect entry { type, config? }
|
|
493
|
+
EffectType, // 'loyalty' | 'transactional' | 'webhook' | 'tag' | 'appRecord' | 'segment'
|
|
494
|
+
EffectConfig, // Union of all effect config shapes
|
|
495
|
+
LoyaltyEffectConfig,
|
|
496
|
+
TransactionalEffectConfig,
|
|
497
|
+
WebhookEffectConfig,
|
|
498
|
+
TagEffectConfig,
|
|
499
|
+
AppRecordEffectConfig,
|
|
500
|
+
SegmentEffectConfig,
|
|
501
|
+
InteractionEventContext, // Token interpolation context shape
|
|
302
502
|
} from '@proveanything/smartlinks';
|
|
303
503
|
```
|
|
304
504
|
|
package/dist/openapi.yaml
CHANGED
|
@@ -21121,6 +21121,10 @@ components:
|
|
|
21121
21121
|
$ref: "#/components/schemas/InteractionDisplay"
|
|
21122
21122
|
interactionType:
|
|
21123
21123
|
type: string
|
|
21124
|
+
effects:
|
|
21125
|
+
type: array
|
|
21126
|
+
items:
|
|
21127
|
+
$ref: "#/components/schemas/InteractionEffect"
|
|
21124
21128
|
createdAt:
|
|
21125
21129
|
type: string
|
|
21126
21130
|
required:
|
|
@@ -21196,6 +21200,183 @@ components:
|
|
|
21196
21200
|
- FORBIDDEN
|
|
21197
21201
|
required:
|
|
21198
21202
|
- error
|
|
21203
|
+
InteractionEffect:
|
|
21204
|
+
type: object
|
|
21205
|
+
properties:
|
|
21206
|
+
type:
|
|
21207
|
+
$ref: "#/components/schemas/EffectType"
|
|
21208
|
+
config:
|
|
21209
|
+
$ref: "#/components/schemas/EffectConfig"
|
|
21210
|
+
required:
|
|
21211
|
+
- type
|
|
21212
|
+
LoyaltyEffectConfig:
|
|
21213
|
+
type: object
|
|
21214
|
+
properties: {}
|
|
21215
|
+
TransactionalEffectConfig:
|
|
21216
|
+
type: object
|
|
21217
|
+
properties:
|
|
21218
|
+
templateId:
|
|
21219
|
+
type: string
|
|
21220
|
+
channel:
|
|
21221
|
+
type: string
|
|
21222
|
+
enum:
|
|
21223
|
+
- email
|
|
21224
|
+
- sms
|
|
21225
|
+
- push
|
|
21226
|
+
- whatsapp
|
|
21227
|
+
- wallet
|
|
21228
|
+
- preferred
|
|
21229
|
+
props:
|
|
21230
|
+
type: object
|
|
21231
|
+
additionalProperties: true
|
|
21232
|
+
include:
|
|
21233
|
+
type: object
|
|
21234
|
+
additionalProperties: true
|
|
21235
|
+
productId:
|
|
21236
|
+
type: string
|
|
21237
|
+
proofId:
|
|
21238
|
+
type: string
|
|
21239
|
+
user:
|
|
21240
|
+
type: boolean
|
|
21241
|
+
appCase:
|
|
21242
|
+
type: string
|
|
21243
|
+
appThread:
|
|
21244
|
+
type: string
|
|
21245
|
+
appRecord:
|
|
21246
|
+
type: string
|
|
21247
|
+
appId:
|
|
21248
|
+
type: string
|
|
21249
|
+
required:
|
|
21250
|
+
- templateId
|
|
21251
|
+
WebhookEffectConfig:
|
|
21252
|
+
type: object
|
|
21253
|
+
properties:
|
|
21254
|
+
url:
|
|
21255
|
+
type: string
|
|
21256
|
+
method:
|
|
21257
|
+
type: string
|
|
21258
|
+
enum:
|
|
21259
|
+
- GET
|
|
21260
|
+
- POST
|
|
21261
|
+
- PUT
|
|
21262
|
+
- PATCH
|
|
21263
|
+
- DELETE
|
|
21264
|
+
headers:
|
|
21265
|
+
type: object
|
|
21266
|
+
additionalProperties:
|
|
21267
|
+
type: string
|
|
21268
|
+
body:
|
|
21269
|
+
type: object
|
|
21270
|
+
additionalProperties: true
|
|
21271
|
+
timeout:
|
|
21272
|
+
type: number
|
|
21273
|
+
required:
|
|
21274
|
+
- url
|
|
21275
|
+
TagEffectConfig:
|
|
21276
|
+
type: object
|
|
21277
|
+
properties:
|
|
21278
|
+
tags:
|
|
21279
|
+
type: array
|
|
21280
|
+
items:
|
|
21281
|
+
type: string
|
|
21282
|
+
action:
|
|
21283
|
+
type: string
|
|
21284
|
+
enum:
|
|
21285
|
+
- add
|
|
21286
|
+
- remove
|
|
21287
|
+
required:
|
|
21288
|
+
- tags
|
|
21289
|
+
AppRecordEffectConfig:
|
|
21290
|
+
type: object
|
|
21291
|
+
properties:
|
|
21292
|
+
appId:
|
|
21293
|
+
type: string
|
|
21294
|
+
recordType:
|
|
21295
|
+
type: string
|
|
21296
|
+
singletonPer:
|
|
21297
|
+
type: string
|
|
21298
|
+
data:
|
|
21299
|
+
type: object
|
|
21300
|
+
additionalProperties: true
|
|
21301
|
+
anchors:
|
|
21302
|
+
type: object
|
|
21303
|
+
additionalProperties: true
|
|
21304
|
+
productId:
|
|
21305
|
+
type: string
|
|
21306
|
+
proofId:
|
|
21307
|
+
type: string
|
|
21308
|
+
variantId:
|
|
21309
|
+
type: string
|
|
21310
|
+
batchId:
|
|
21311
|
+
type: string
|
|
21312
|
+
SegmentEffectConfig:
|
|
21313
|
+
type: object
|
|
21314
|
+
properties:
|
|
21315
|
+
segmentId:
|
|
21316
|
+
type: string
|
|
21317
|
+
action:
|
|
21318
|
+
type: string
|
|
21319
|
+
enum:
|
|
21320
|
+
- add
|
|
21321
|
+
- remove
|
|
21322
|
+
required:
|
|
21323
|
+
- segmentId
|
|
21324
|
+
InteractionEventContext:
|
|
21325
|
+
type: object
|
|
21326
|
+
properties:
|
|
21327
|
+
eventId:
|
|
21328
|
+
type: string
|
|
21329
|
+
collectionId:
|
|
21330
|
+
type: string
|
|
21331
|
+
appId:
|
|
21332
|
+
type: string
|
|
21333
|
+
interactionId:
|
|
21334
|
+
type: string
|
|
21335
|
+
contactId:
|
|
21336
|
+
type: string
|
|
21337
|
+
userId:
|
|
21338
|
+
type: string
|
|
21339
|
+
productId:
|
|
21340
|
+
type: string
|
|
21341
|
+
proofId:
|
|
21342
|
+
type: string
|
|
21343
|
+
variantId:
|
|
21344
|
+
type: string
|
|
21345
|
+
batchId:
|
|
21346
|
+
type: string
|
|
21347
|
+
outcome:
|
|
21348
|
+
type: string
|
|
21349
|
+
eventType:
|
|
21350
|
+
type: string
|
|
21351
|
+
source:
|
|
21352
|
+
type: string
|
|
21353
|
+
timestamp:
|
|
21354
|
+
type: string
|
|
21355
|
+
metadata:
|
|
21356
|
+
type: object
|
|
21357
|
+
additionalProperties: true
|
|
21358
|
+
broadcastId:
|
|
21359
|
+
type: string
|
|
21360
|
+
journeyId:
|
|
21361
|
+
type: string
|
|
21362
|
+
required:
|
|
21363
|
+
- eventId
|
|
21364
|
+
- collectionId
|
|
21365
|
+
- appId
|
|
21366
|
+
- interactionId
|
|
21367
|
+
- contactId
|
|
21368
|
+
- userId
|
|
21369
|
+
- productId
|
|
21370
|
+
- proofId
|
|
21371
|
+
- variantId
|
|
21372
|
+
- batchId
|
|
21373
|
+
- outcome
|
|
21374
|
+
- eventType
|
|
21375
|
+
- source
|
|
21376
|
+
- timestamp
|
|
21377
|
+
- metadata
|
|
21378
|
+
- broadcastId
|
|
21379
|
+
- journeyId
|
|
21199
21380
|
Job:
|
|
21200
21381
|
type: object
|
|
21201
21382
|
properties:
|
|
@@ -22927,9 +23108,6 @@ components:
|
|
|
22927
23108
|
required:
|
|
22928
23109
|
- key
|
|
22929
23110
|
- name
|
|
22930
|
-
ProductFacetMap:
|
|
22931
|
-
type: object
|
|
22932
|
-
properties: {}
|
|
22933
23111
|
ProductQueryRequest:
|
|
22934
23112
|
type: object
|
|
22935
23113
|
properties:
|
|
@@ -185,6 +185,7 @@ export interface InteractionTypeRecord {
|
|
|
185
185
|
display?: InteractionDisplay;
|
|
186
186
|
scopes?: Record<string, InteractionDisplay>;
|
|
187
187
|
interactionType?: string;
|
|
188
|
+
effects?: InteractionEffect[];
|
|
188
189
|
[key: string]: unknown;
|
|
189
190
|
};
|
|
190
191
|
createdAt: string;
|
|
@@ -218,3 +219,99 @@ export interface SubmitInteractionError {
|
|
|
218
219
|
error: 'FORBIDDEN';
|
|
219
220
|
reason: 'not_public' | 'auth_required' | 'duplicate' | 'duplicate_anon' | 'disabled' | 'before_start' | 'after_end' | 'origin_forbidden';
|
|
220
221
|
}
|
|
222
|
+
export type EffectType = 'loyalty' | 'transactional' | 'webhook' | 'tag' | 'appRecord' | 'segment';
|
|
223
|
+
export interface InteractionEffect {
|
|
224
|
+
type: EffectType;
|
|
225
|
+
config?: EffectConfig;
|
|
226
|
+
}
|
|
227
|
+
export type EffectConfig = LoyaltyEffectConfig | TransactionalEffectConfig | WebhookEffectConfig | TagEffectConfig | AppRecordEffectConfig | SegmentEffectConfig;
|
|
228
|
+
/** No config required — earning rules are driven by the interactionId */
|
|
229
|
+
export interface LoyaltyEffectConfig {
|
|
230
|
+
[key: string]: never;
|
|
231
|
+
}
|
|
232
|
+
export interface TransactionalEffectConfig {
|
|
233
|
+
/** Required. Firestore template ID */
|
|
234
|
+
templateId: string;
|
|
235
|
+
/**
|
|
236
|
+
* Channel to use.
|
|
237
|
+
* Default: 'preferred' — auto-selects the contact's best available channel.
|
|
238
|
+
*/
|
|
239
|
+
channel?: 'email' | 'sms' | 'push' | 'whatsapp' | 'wallet' | 'preferred';
|
|
240
|
+
/** Additional Liquid variables. Supports {{token}} interpolation. */
|
|
241
|
+
props?: Record<string, unknown>;
|
|
242
|
+
/** Hydration directives. productId/proofId default to the event's own values. */
|
|
243
|
+
include?: {
|
|
244
|
+
productId?: string;
|
|
245
|
+
proofId?: string;
|
|
246
|
+
user?: boolean;
|
|
247
|
+
appCase?: string;
|
|
248
|
+
appThread?: string;
|
|
249
|
+
appRecord?: string;
|
|
250
|
+
};
|
|
251
|
+
/** Override the appId logged on the comms event row. */
|
|
252
|
+
appId?: string;
|
|
253
|
+
}
|
|
254
|
+
export interface WebhookEffectConfig {
|
|
255
|
+
/** Required. Target URL. Supports {{token}} interpolation. */
|
|
256
|
+
url: string;
|
|
257
|
+
/** HTTP verb. Default: 'POST' */
|
|
258
|
+
method?: 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE';
|
|
259
|
+
/** Additional HTTP headers. Supports {{token}} interpolation on values. */
|
|
260
|
+
headers?: Record<string, string>;
|
|
261
|
+
/** JSON body template. Supports {{token}} interpolation on string values. */
|
|
262
|
+
body?: Record<string, unknown>;
|
|
263
|
+
/** Request timeout in milliseconds. Default: 10000 */
|
|
264
|
+
timeout?: number;
|
|
265
|
+
}
|
|
266
|
+
export interface TagEffectConfig {
|
|
267
|
+
/** One or more tag strings to add or remove. Supports {{token}} interpolation. */
|
|
268
|
+
tags: string[];
|
|
269
|
+
/** Default: 'add' */
|
|
270
|
+
action?: 'add' | 'remove';
|
|
271
|
+
}
|
|
272
|
+
export interface AppRecordEffectConfig {
|
|
273
|
+
/** Override the appId for the created record. */
|
|
274
|
+
appId?: string;
|
|
275
|
+
/** Record type identifier. Default: 'default' */
|
|
276
|
+
recordType?: string;
|
|
277
|
+
/**
|
|
278
|
+
* Singleton cardinality key. At most one record per recordType+singletonPer will
|
|
279
|
+
* exist per scope. Common values: 'contact', 'product', 'proof', 'global'
|
|
280
|
+
*/
|
|
281
|
+
singletonPer?: string;
|
|
282
|
+
/** Data object stored on the record. Supports {{token}} interpolation. */
|
|
283
|
+
data?: Record<string, unknown>;
|
|
284
|
+
/** Override scope anchors. Defaults to the event's own ids. */
|
|
285
|
+
anchors?: {
|
|
286
|
+
productId?: string;
|
|
287
|
+
proofId?: string;
|
|
288
|
+
variantId?: string;
|
|
289
|
+
batchId?: string;
|
|
290
|
+
};
|
|
291
|
+
}
|
|
292
|
+
export interface SegmentEffectConfig {
|
|
293
|
+
/** Required. UUID of the static segment to modify */
|
|
294
|
+
segmentId: string;
|
|
295
|
+
/** Default: 'add' */
|
|
296
|
+
action?: 'add' | 'remove';
|
|
297
|
+
}
|
|
298
|
+
/** Shape of the event context used for {{token}} interpolation in effect configs */
|
|
299
|
+
export interface InteractionEventContext {
|
|
300
|
+
eventId: string | null;
|
|
301
|
+
collectionId: string;
|
|
302
|
+
appId: string | null;
|
|
303
|
+
interactionId: string | null;
|
|
304
|
+
contactId: string | null;
|
|
305
|
+
userId: string | null;
|
|
306
|
+
productId: string | null;
|
|
307
|
+
proofId: string | null;
|
|
308
|
+
variantId: string | null;
|
|
309
|
+
batchId: string | null;
|
|
310
|
+
outcome: string | null;
|
|
311
|
+
eventType: string | null;
|
|
312
|
+
source: string | null;
|
|
313
|
+
timestamp: string | null;
|
|
314
|
+
metadata: Record<string, unknown>;
|
|
315
|
+
broadcastId: string | null;
|
|
316
|
+
journeyId: string | null;
|
|
317
|
+
}
|
package/dist/types/product.d.ts
CHANGED
|
@@ -15,6 +15,10 @@ export interface AdditionalGtin {
|
|
|
15
15
|
gtin: string;
|
|
16
16
|
owner?: boolean | null;
|
|
17
17
|
}
|
|
18
|
+
/**
|
|
19
|
+
* Full facet value definition — returned by the Facets API.
|
|
20
|
+
* Not embedded in product responses; use ProductFacetMap for product-level assignments.
|
|
21
|
+
*/
|
|
18
22
|
export interface ProductFacetValue {
|
|
19
23
|
id?: string;
|
|
20
24
|
key: string;
|
|
@@ -25,9 +29,14 @@ export interface ProductFacetValue {
|
|
|
25
29
|
color?: string;
|
|
26
30
|
icon?: string;
|
|
27
31
|
}
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
32
|
+
/**
|
|
33
|
+
* Slim facet assignments on a product: maps each facet key to an array of assigned
|
|
34
|
+
* value slugs/keys. Full value metadata lives in the Facets API.
|
|
35
|
+
*
|
|
36
|
+
* @example
|
|
37
|
+
* { type: ['website'], certifications: ['organic', 'recycled'] }
|
|
38
|
+
*/
|
|
39
|
+
export type ProductFacetMap = Record<string, string[]>;
|
|
31
40
|
export interface ProductQueryRequest {
|
|
32
41
|
query?: {
|
|
33
42
|
search?: string;
|
|
@@ -219,22 +219,15 @@ export interface ProductInfo {
|
|
|
219
219
|
id: string;
|
|
220
220
|
tags?: Record<string, any>;
|
|
221
221
|
/**
|
|
222
|
-
* Facet
|
|
223
|
-
*
|
|
224
|
-
* Each value object must have at minimum a `key` string property.
|
|
222
|
+
* Facet assignments on this product: maps each facet key to an array of assigned
|
|
223
|
+
* value slugs/keys. Matches the slim shape returned by the Products API.
|
|
225
224
|
*
|
|
226
225
|
* @example
|
|
227
226
|
* ```ts
|
|
228
|
-
* {
|
|
229
|
-
* material: [{ key: 'cotton', name: 'Cotton' }],
|
|
230
|
-
* certifications: [{ key: 'organic', name: 'Organic' }, { key: 'recycled', name: 'Recycled' }]
|
|
231
|
-
* }
|
|
227
|
+
* { material: ['cotton'], certifications: ['organic', 'recycled'] }
|
|
232
228
|
* ```
|
|
233
229
|
*/
|
|
234
|
-
facets?: Record<string,
|
|
235
|
-
key: string;
|
|
236
|
-
[k: string]: unknown;
|
|
237
|
-
}>>;
|
|
230
|
+
facets?: Record<string, string[]>;
|
|
238
231
|
}
|
|
239
232
|
/**
|
|
240
233
|
* Proof information for condition validation
|
package/dist/utils/conditions.js
CHANGED
|
@@ -753,20 +753,19 @@ async function validateFacet(condition, params) {
|
|
|
753
753
|
context: { facetKey, matchMode },
|
|
754
754
|
};
|
|
755
755
|
}
|
|
756
|
-
const
|
|
757
|
-
const assignedKeys = assigned.map(v => v.key);
|
|
756
|
+
const assignedKeys = (_c = facets === null || facets === void 0 ? void 0 : facets[facetKey]) !== null && _c !== void 0 ? _c : [];
|
|
758
757
|
// Presence-only modes — ignore `values`
|
|
759
758
|
if (matchMode === 'hasFacet') {
|
|
760
759
|
return {
|
|
761
760
|
passed: assignedKeys.length > 0,
|
|
762
|
-
detail: `Product ${
|
|
761
|
+
detail: `Product ${assignedKeys.length > 0 ? 'has' : 'does not have'} values on facet '${facetKey}'.`,
|
|
763
762
|
context: { facetKey, assignedKeys },
|
|
764
763
|
};
|
|
765
764
|
}
|
|
766
765
|
if (matchMode === 'notHasFacet') {
|
|
767
766
|
return {
|
|
768
767
|
passed: assignedKeys.length === 0,
|
|
769
|
-
detail: `Product ${
|
|
768
|
+
detail: `Product ${assignedKeys.length === 0 ? 'has no' : 'has'} values on facet '${facetKey}'.`,
|
|
770
769
|
context: { facetKey, assignedKeys },
|
|
771
770
|
};
|
|
772
771
|
}
|
package/docs/API_SUMMARY.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Smartlinks API Summary
|
|
2
2
|
|
|
3
|
-
Version: 1.13.
|
|
3
|
+
Version: 1.13.10 | Generated: 2026-05-12T16:12:33.784Z
|
|
4
4
|
|
|
5
5
|
This is a concise summary of all available API functions and types.
|
|
6
6
|
|
|
@@ -5457,8 +5457,9 @@ interface InteractionTypeRecord {
|
|
|
5457
5457
|
permissions?: InteractionPermissions
|
|
5458
5458
|
data?: {
|
|
5459
5459
|
display?: InteractionDisplay
|
|
5460
|
-
scopes?: Record<string, InteractionDisplay
|
|
5460
|
+
scopes?: Record<string, InteractionDisplay>
|
|
5461
5461
|
interactionType?: string
|
|
5462
|
+
effects?: InteractionEffect[]
|
|
5462
5463
|
[key: string]: unknown
|
|
5463
5464
|
}
|
|
5464
5465
|
createdAt: string
|
|
@@ -5526,6 +5527,113 @@ interface SubmitInteractionError {
|
|
|
5526
5527
|
}
|
|
5527
5528
|
```
|
|
5528
5529
|
|
|
5530
|
+
**InteractionEffect** (interface)
|
|
5531
|
+
```typescript
|
|
5532
|
+
interface InteractionEffect {
|
|
5533
|
+
type: EffectType
|
|
5534
|
+
config?: EffectConfig
|
|
5535
|
+
}
|
|
5536
|
+
```
|
|
5537
|
+
|
|
5538
|
+
**LoyaltyEffectConfig** (interface)
|
|
5539
|
+
```typescript
|
|
5540
|
+
interface LoyaltyEffectConfig {
|
|
5541
|
+
[key: string]: never
|
|
5542
|
+
}
|
|
5543
|
+
```
|
|
5544
|
+
|
|
5545
|
+
**TransactionalEffectConfig** (interface)
|
|
5546
|
+
```typescript
|
|
5547
|
+
interface TransactionalEffectConfig {
|
|
5548
|
+
templateId: string
|
|
5549
|
+
* Channel to use.
|
|
5550
|
+
* Default: 'preferred' — auto-selects the contact's best available channel.
|
|
5551
|
+
channel?: 'email' | 'sms' | 'push' | 'whatsapp' | 'wallet' | 'preferred'
|
|
5552
|
+
props?: Record<string, unknown>
|
|
5553
|
+
include?: {
|
|
5554
|
+
productId?: string
|
|
5555
|
+
proofId?: string
|
|
5556
|
+
user?: boolean
|
|
5557
|
+
appCase?: string
|
|
5558
|
+
appThread?: string
|
|
5559
|
+
appRecord?: string
|
|
5560
|
+
}
|
|
5561
|
+
appId?: string
|
|
5562
|
+
}
|
|
5563
|
+
```
|
|
5564
|
+
|
|
5565
|
+
**WebhookEffectConfig** (interface)
|
|
5566
|
+
```typescript
|
|
5567
|
+
interface WebhookEffectConfig {
|
|
5568
|
+
url: string
|
|
5569
|
+
method?: 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE'
|
|
5570
|
+
headers?: Record<string, string>
|
|
5571
|
+
body?: Record<string, unknown>
|
|
5572
|
+
timeout?: number
|
|
5573
|
+
}
|
|
5574
|
+
```
|
|
5575
|
+
|
|
5576
|
+
**TagEffectConfig** (interface)
|
|
5577
|
+
```typescript
|
|
5578
|
+
interface TagEffectConfig {
|
|
5579
|
+
tags: string[]
|
|
5580
|
+
action?: 'add' | 'remove'
|
|
5581
|
+
}
|
|
5582
|
+
```
|
|
5583
|
+
|
|
5584
|
+
**AppRecordEffectConfig** (interface)
|
|
5585
|
+
```typescript
|
|
5586
|
+
interface AppRecordEffectConfig {
|
|
5587
|
+
appId?: string
|
|
5588
|
+
recordType?: string
|
|
5589
|
+
* Singleton cardinality key. At most one record per recordType+singletonPer will
|
|
5590
|
+
* exist per scope. Common values: 'contact', 'product', 'proof', 'global'
|
|
5591
|
+
singletonPer?: string
|
|
5592
|
+
data?: Record<string, unknown>
|
|
5593
|
+
anchors?: {
|
|
5594
|
+
productId?: string
|
|
5595
|
+
proofId?: string
|
|
5596
|
+
variantId?: string
|
|
5597
|
+
batchId?: string
|
|
5598
|
+
}
|
|
5599
|
+
}
|
|
5600
|
+
```
|
|
5601
|
+
|
|
5602
|
+
**SegmentEffectConfig** (interface)
|
|
5603
|
+
```typescript
|
|
5604
|
+
interface SegmentEffectConfig {
|
|
5605
|
+
segmentId: string
|
|
5606
|
+
action?: 'add' | 'remove'
|
|
5607
|
+
}
|
|
5608
|
+
```
|
|
5609
|
+
|
|
5610
|
+
**InteractionEventContext** (interface)
|
|
5611
|
+
```typescript
|
|
5612
|
+
interface InteractionEventContext {
|
|
5613
|
+
eventId: string | null
|
|
5614
|
+
collectionId: string
|
|
5615
|
+
appId: string | null
|
|
5616
|
+
interactionId: string | null
|
|
5617
|
+
contactId: string | null
|
|
5618
|
+
userId: string | null
|
|
5619
|
+
productId: string | null
|
|
5620
|
+
proofId: string | null
|
|
5621
|
+
variantId: string | null
|
|
5622
|
+
batchId: string | null
|
|
5623
|
+
outcome: string | null
|
|
5624
|
+
eventType: string | null
|
|
5625
|
+
source: string | null
|
|
5626
|
+
timestamp: string | null
|
|
5627
|
+
metadata: Record<string, unknown>
|
|
5628
|
+
broadcastId: string | null
|
|
5629
|
+
journeyId: string | null
|
|
5630
|
+
}
|
|
5631
|
+
```
|
|
5632
|
+
|
|
5633
|
+
**EffectType** = ``
|
|
5634
|
+
|
|
5635
|
+
**EffectConfig** = ``
|
|
5636
|
+
|
|
5529
5637
|
### jobs
|
|
5530
5638
|
|
|
5531
5639
|
**Job** (interface)
|
|
@@ -6581,13 +6689,6 @@ interface ProductFacetValue {
|
|
|
6581
6689
|
}
|
|
6582
6690
|
```
|
|
6583
6691
|
|
|
6584
|
-
**ProductFacetMap** (interface)
|
|
6585
|
-
```typescript
|
|
6586
|
-
interface ProductFacetMap {
|
|
6587
|
-
[facetKey: string]: ProductFacetValue[]
|
|
6588
|
-
}
|
|
6589
|
-
```
|
|
6590
|
-
|
|
6591
6692
|
**ProductQueryRequest** (interface)
|
|
6592
6693
|
```typescript
|
|
6593
6694
|
interface ProductQueryRequest {
|
|
@@ -6674,6 +6775,8 @@ interface ProductQueryResponse {
|
|
|
6674
6775
|
|
|
6675
6776
|
**ISODateString** = `string`
|
|
6676
6777
|
|
|
6778
|
+
**ProductFacetMap** = `Record<string, string[]>`
|
|
6779
|
+
|
|
6677
6780
|
**ProductClaimCreateRequestBody** = `Omit<ProductClaimCreateInput, 'collectionId' | 'id'>`
|
|
6678
6781
|
|
|
6679
6782
|
**ProductResponse** = `Product`
|
|
@@ -7414,18 +7517,14 @@ interface UserInfo {
|
|
|
7414
7517
|
interface ProductInfo {
|
|
7415
7518
|
id: string
|
|
7416
7519
|
tags?: Record<string, any>
|
|
7417
|
-
* Facet
|
|
7418
|
-
*
|
|
7419
|
-
* Each value object must have at minimum a `key` string property.
|
|
7520
|
+
* Facet assignments on this product: maps each facet key to an array of assigned
|
|
7521
|
+
* value slugs/keys. Matches the slim shape returned by the Products API.
|
|
7420
7522
|
*
|
|
7421
7523
|
* @example
|
|
7422
7524
|
* ```ts
|
|
7423
|
-
* {
|
|
7424
|
-
* material: [{ key: 'cotton', name: 'Cotton' }],
|
|
7425
|
-
* certifications: [{ key: 'organic', name: 'Organic' }, { key: 'recycled', name: 'Recycled' }]
|
|
7426
|
-
* }
|
|
7525
|
+
* { material: ['cotton'], certifications: ['organic', 'recycled'] }
|
|
7427
7526
|
* ```
|
|
7428
|
-
facets?: Record<string,
|
|
7527
|
+
facets?: Record<string, string[]>
|
|
7429
7528
|
}
|
|
7430
7529
|
```
|
|
7431
7530
|
|
package/docs/interactions.md
CHANGED
|
@@ -280,6 +280,195 @@ When defining a journey trigger, reference the `interactionId` that should fire
|
|
|
280
280
|
|
|
281
281
|
---
|
|
282
282
|
|
|
283
|
+
## Interaction Effects
|
|
284
|
+
|
|
285
|
+
**Interaction effects** are automatic side-effects that fire immediately after an interaction event is recorded. They are defined directly on the interaction definition (`data.effects`) and run inline, fire-and-forget — they never block or fail the interaction response.
|
|
286
|
+
|
|
287
|
+
Common use cases: send a confirmation email, post a webhook to a CRM, tag the contact, award loyalty points, create an app record, or add the contact to a segment.
|
|
288
|
+
|
|
289
|
+
### How It Works
|
|
290
|
+
|
|
291
|
+
1. Event is logged to BigQuery
|
|
292
|
+
2. Interaction definition is loaded
|
|
293
|
+
3. Each configured effect runs **in order**, fire-and-forget
|
|
294
|
+
4. Response is returned to the caller immediately
|
|
295
|
+
|
|
296
|
+
A failure in one effect is logged and swallowed; subsequent effects still run.
|
|
297
|
+
|
|
298
|
+
> **Note:** `transactional` and `webhook` effects currently run inline. They are planned to move to background jobs for retry-on-failure support once the jobs infrastructure is fully operational.
|
|
299
|
+
|
|
300
|
+
### Configuring Effects
|
|
301
|
+
|
|
302
|
+
Pass `data.effects` when creating or updating an interaction type:
|
|
303
|
+
|
|
304
|
+
```typescript
|
|
305
|
+
await SL.interactions.create(collectionId, {
|
|
306
|
+
id: 'donation',
|
|
307
|
+
appId: 'my-app',
|
|
308
|
+
permissions: { allowPublicSubmit: true },
|
|
309
|
+
data: {
|
|
310
|
+
effects: [
|
|
311
|
+
{
|
|
312
|
+
type: 'transactional',
|
|
313
|
+
config: {
|
|
314
|
+
templateId: 'tmpl_donation_receipt',
|
|
315
|
+
channel: 'email',
|
|
316
|
+
props: {
|
|
317
|
+
donationAmount: '{{metadata.amount}}',
|
|
318
|
+
campaignName: '{{metadata.campaign}}',
|
|
319
|
+
},
|
|
320
|
+
},
|
|
321
|
+
},
|
|
322
|
+
{
|
|
323
|
+
type: 'tag',
|
|
324
|
+
config: { tags: ['donor'] },
|
|
325
|
+
},
|
|
326
|
+
],
|
|
327
|
+
},
|
|
328
|
+
});
|
|
329
|
+
```
|
|
330
|
+
|
|
331
|
+
### Token Interpolation
|
|
332
|
+
|
|
333
|
+
All effect `config` values support `{{token}}` interpolation resolved from the event context at runtime. Nested paths using dot notation are supported (e.g. `{{metadata.amount}}`).
|
|
334
|
+
|
|
335
|
+
| Token | Description |
|
|
336
|
+
|---|---|
|
|
337
|
+
| `{{eventId}}` | BigQuery event UUID |
|
|
338
|
+
| `{{collectionId}}` | Collection / brand ID |
|
|
339
|
+
| `{{appId}}` | App ID that submitted the event |
|
|
340
|
+
| `{{interactionId}}` | Interaction definition ID |
|
|
341
|
+
| `{{contactId}}` | Contact UUID (if resolved) |
|
|
342
|
+
| `{{userId}}` | Firebase UID (if authenticated) |
|
|
343
|
+
| `{{productId}}` | Product ID (if provided with event) |
|
|
344
|
+
| `{{proofId}}` | Proof ID (if provided with event) |
|
|
345
|
+
| `{{variantId}}` | Variant ID (if provided with event) |
|
|
346
|
+
| `{{batchId}}` | Batch ID (if provided with event) |
|
|
347
|
+
| `{{outcome}}` | Event outcome (e.g. `"submitted"`) |
|
|
348
|
+
| `{{eventType}}` | Event type string |
|
|
349
|
+
| `{{source}}` | Event source string |
|
|
350
|
+
| `{{timestamp}}` | ISO 8601 event timestamp |
|
|
351
|
+
| `{{metadata.*}}` | Any key from the event `metadata` object |
|
|
352
|
+
|
|
353
|
+
Tokens that resolve to `null` or `undefined` become an empty string.
|
|
354
|
+
|
|
355
|
+
### Effect Types
|
|
356
|
+
|
|
357
|
+
#### `loyalty`
|
|
358
|
+
|
|
359
|
+
Awards loyalty points by evaluating `LoyaltyEarningRule` records configured against this interaction. Earning rules are managed separately via the Loyalty API. If no rules are configured, this effect is a no-op.
|
|
360
|
+
|
|
361
|
+
```json
|
|
362
|
+
{ "type": "loyalty", "config": {} }
|
|
363
|
+
```
|
|
364
|
+
|
|
365
|
+
#### `transactional`
|
|
366
|
+
|
|
367
|
+
Sends a message to the contact using a comms template. Supports all channels with full Liquid template hydration.
|
|
368
|
+
|
|
369
|
+
```json
|
|
370
|
+
{
|
|
371
|
+
"type": "transactional",
|
|
372
|
+
"config": {
|
|
373
|
+
"templateId": "tmpl_donation_receipt",
|
|
374
|
+
"channel": "email",
|
|
375
|
+
"props": {
|
|
376
|
+
"donationAmount": "{{metadata.amount}}"
|
|
377
|
+
}
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
```
|
|
381
|
+
|
|
382
|
+
`channel` defaults to `"preferred"` — auto-selects the contact's best available channel respecting consent, suppression, and template availability. Optionally pass `include` to hydrate product/proof/user context, and `props` for additional Liquid variables.
|
|
383
|
+
|
|
384
|
+
#### `webhook`
|
|
385
|
+
|
|
386
|
+
Posts a JSON payload to an HTTP endpoint. All config values support `{{token}}` interpolation.
|
|
387
|
+
|
|
388
|
+
```json
|
|
389
|
+
{
|
|
390
|
+
"type": "webhook",
|
|
391
|
+
"config": {
|
|
392
|
+
"url": "https://crm.example.com/api/events",
|
|
393
|
+
"headers": { "Authorization": "Bearer sk_live_abc123" },
|
|
394
|
+
"body": {
|
|
395
|
+
"contactId": "{{contactId}}",
|
|
396
|
+
"amount": "{{metadata.amount}}"
|
|
397
|
+
},
|
|
398
|
+
"timeout": 8000
|
|
399
|
+
}
|
|
400
|
+
}
|
|
401
|
+
```
|
|
402
|
+
|
|
403
|
+
When `body` is omitted, a standard payload is sent containing `eventId`, `collectionId`, `appId`, `interactionId`, `contactId`, `userId`, `productId`, `proofId`, `outcome`, `eventType`, `metadata`, and `timestamp`.
|
|
404
|
+
|
|
405
|
+
#### `tag`
|
|
406
|
+
|
|
407
|
+
Adds or removes string tags on the contact record. Skipped if there is no `contactId` on the event.
|
|
408
|
+
|
|
409
|
+
```json
|
|
410
|
+
{
|
|
411
|
+
"type": "tag",
|
|
412
|
+
"config": {
|
|
413
|
+
"tags": ["donor", "tier-{{metadata.tier}}"],
|
|
414
|
+
"action": "add"
|
|
415
|
+
}
|
|
416
|
+
}
|
|
417
|
+
```
|
|
418
|
+
|
|
419
|
+
`action` defaults to `"add"`. Duplicate adds are no-ops; removing a tag not present is a no-op.
|
|
420
|
+
|
|
421
|
+
#### `appRecord`
|
|
422
|
+
|
|
423
|
+
Creates or upserts an app record scoped to the event context. Use `singletonPer` for cardinality control — if a matching singleton already exists it is updated rather than duplicated.
|
|
424
|
+
|
|
425
|
+
```json
|
|
426
|
+
{
|
|
427
|
+
"type": "appRecord",
|
|
428
|
+
"config": {
|
|
429
|
+
"recordType": "participation",
|
|
430
|
+
"singletonPer": "contact",
|
|
431
|
+
"data": {
|
|
432
|
+
"participatedAt": "{{timestamp}}",
|
|
433
|
+
"outcome": "{{outcome}}"
|
|
434
|
+
}
|
|
435
|
+
}
|
|
436
|
+
}
|
|
437
|
+
```
|
|
438
|
+
|
|
439
|
+
Common `singletonPer` values: `"contact"`, `"product"`, `"proof"`, `"global"`.
|
|
440
|
+
|
|
441
|
+
#### `segment`
|
|
442
|
+
|
|
443
|
+
Adds or removes the contact from a **static** segment. Skipped if there is no `contactId` on the event.
|
|
444
|
+
|
|
445
|
+
```json
|
|
446
|
+
{
|
|
447
|
+
"type": "segment",
|
|
448
|
+
"config": {
|
|
449
|
+
"segmentId": "seg_donors_2026",
|
|
450
|
+
"action": "add"
|
|
451
|
+
}
|
|
452
|
+
}
|
|
453
|
+
```
|
|
454
|
+
|
|
455
|
+
The segment must already exist and be of `filterType: 'static'`.
|
|
456
|
+
|
|
457
|
+
### Effects Error Handling
|
|
458
|
+
|
|
459
|
+
| Scenario | Behaviour |
|
|
460
|
+
|---|---|
|
|
461
|
+
| Effect throws (e.g. template not found, webhook 500) | Error logged server-side; next effect continues |
|
|
462
|
+
| Contact has no email, `transactional` channel = `email` | Effect throws, logged, skipped |
|
|
463
|
+
| `contactId` absent on anonymous event | `tag`, `appRecord`, `segment` log a warning and skip |
|
|
464
|
+
| Unknown `type` value | Warning logged, effect skipped |
|
|
465
|
+
| `loyalty` — no earning rules configured | Silent no-op |
|
|
466
|
+
| Interaction has no `data.effects` | No effects run, no overhead |
|
|
467
|
+
|
|
468
|
+
Effects do **not** propagate errors back to the API caller.
|
|
469
|
+
|
|
470
|
+
---
|
|
471
|
+
|
|
283
472
|
## TypeScript Types
|
|
284
473
|
|
|
285
474
|
```typescript
|
|
@@ -299,6 +488,17 @@ import type {
|
|
|
299
488
|
AdminInteractionsCountsByOutcomeRequest,
|
|
300
489
|
PublicInteractionsCountsByOutcomeRequest,
|
|
301
490
|
PublicInteractionsByUserRequest,
|
|
491
|
+
// Effects
|
|
492
|
+
InteractionEffect, // Single effect entry { type, config? }
|
|
493
|
+
EffectType, // 'loyalty' | 'transactional' | 'webhook' | 'tag' | 'appRecord' | 'segment'
|
|
494
|
+
EffectConfig, // Union of all effect config shapes
|
|
495
|
+
LoyaltyEffectConfig,
|
|
496
|
+
TransactionalEffectConfig,
|
|
497
|
+
WebhookEffectConfig,
|
|
498
|
+
TagEffectConfig,
|
|
499
|
+
AppRecordEffectConfig,
|
|
500
|
+
SegmentEffectConfig,
|
|
501
|
+
InteractionEventContext, // Token interpolation context shape
|
|
302
502
|
} from '@proveanything/smartlinks';
|
|
303
503
|
```
|
|
304
504
|
|
package/openapi.yaml
CHANGED
|
@@ -21121,6 +21121,10 @@ components:
|
|
|
21121
21121
|
$ref: "#/components/schemas/InteractionDisplay"
|
|
21122
21122
|
interactionType:
|
|
21123
21123
|
type: string
|
|
21124
|
+
effects:
|
|
21125
|
+
type: array
|
|
21126
|
+
items:
|
|
21127
|
+
$ref: "#/components/schemas/InteractionEffect"
|
|
21124
21128
|
createdAt:
|
|
21125
21129
|
type: string
|
|
21126
21130
|
required:
|
|
@@ -21196,6 +21200,183 @@ components:
|
|
|
21196
21200
|
- FORBIDDEN
|
|
21197
21201
|
required:
|
|
21198
21202
|
- error
|
|
21203
|
+
InteractionEffect:
|
|
21204
|
+
type: object
|
|
21205
|
+
properties:
|
|
21206
|
+
type:
|
|
21207
|
+
$ref: "#/components/schemas/EffectType"
|
|
21208
|
+
config:
|
|
21209
|
+
$ref: "#/components/schemas/EffectConfig"
|
|
21210
|
+
required:
|
|
21211
|
+
- type
|
|
21212
|
+
LoyaltyEffectConfig:
|
|
21213
|
+
type: object
|
|
21214
|
+
properties: {}
|
|
21215
|
+
TransactionalEffectConfig:
|
|
21216
|
+
type: object
|
|
21217
|
+
properties:
|
|
21218
|
+
templateId:
|
|
21219
|
+
type: string
|
|
21220
|
+
channel:
|
|
21221
|
+
type: string
|
|
21222
|
+
enum:
|
|
21223
|
+
- email
|
|
21224
|
+
- sms
|
|
21225
|
+
- push
|
|
21226
|
+
- whatsapp
|
|
21227
|
+
- wallet
|
|
21228
|
+
- preferred
|
|
21229
|
+
props:
|
|
21230
|
+
type: object
|
|
21231
|
+
additionalProperties: true
|
|
21232
|
+
include:
|
|
21233
|
+
type: object
|
|
21234
|
+
additionalProperties: true
|
|
21235
|
+
productId:
|
|
21236
|
+
type: string
|
|
21237
|
+
proofId:
|
|
21238
|
+
type: string
|
|
21239
|
+
user:
|
|
21240
|
+
type: boolean
|
|
21241
|
+
appCase:
|
|
21242
|
+
type: string
|
|
21243
|
+
appThread:
|
|
21244
|
+
type: string
|
|
21245
|
+
appRecord:
|
|
21246
|
+
type: string
|
|
21247
|
+
appId:
|
|
21248
|
+
type: string
|
|
21249
|
+
required:
|
|
21250
|
+
- templateId
|
|
21251
|
+
WebhookEffectConfig:
|
|
21252
|
+
type: object
|
|
21253
|
+
properties:
|
|
21254
|
+
url:
|
|
21255
|
+
type: string
|
|
21256
|
+
method:
|
|
21257
|
+
type: string
|
|
21258
|
+
enum:
|
|
21259
|
+
- GET
|
|
21260
|
+
- POST
|
|
21261
|
+
- PUT
|
|
21262
|
+
- PATCH
|
|
21263
|
+
- DELETE
|
|
21264
|
+
headers:
|
|
21265
|
+
type: object
|
|
21266
|
+
additionalProperties:
|
|
21267
|
+
type: string
|
|
21268
|
+
body:
|
|
21269
|
+
type: object
|
|
21270
|
+
additionalProperties: true
|
|
21271
|
+
timeout:
|
|
21272
|
+
type: number
|
|
21273
|
+
required:
|
|
21274
|
+
- url
|
|
21275
|
+
TagEffectConfig:
|
|
21276
|
+
type: object
|
|
21277
|
+
properties:
|
|
21278
|
+
tags:
|
|
21279
|
+
type: array
|
|
21280
|
+
items:
|
|
21281
|
+
type: string
|
|
21282
|
+
action:
|
|
21283
|
+
type: string
|
|
21284
|
+
enum:
|
|
21285
|
+
- add
|
|
21286
|
+
- remove
|
|
21287
|
+
required:
|
|
21288
|
+
- tags
|
|
21289
|
+
AppRecordEffectConfig:
|
|
21290
|
+
type: object
|
|
21291
|
+
properties:
|
|
21292
|
+
appId:
|
|
21293
|
+
type: string
|
|
21294
|
+
recordType:
|
|
21295
|
+
type: string
|
|
21296
|
+
singletonPer:
|
|
21297
|
+
type: string
|
|
21298
|
+
data:
|
|
21299
|
+
type: object
|
|
21300
|
+
additionalProperties: true
|
|
21301
|
+
anchors:
|
|
21302
|
+
type: object
|
|
21303
|
+
additionalProperties: true
|
|
21304
|
+
productId:
|
|
21305
|
+
type: string
|
|
21306
|
+
proofId:
|
|
21307
|
+
type: string
|
|
21308
|
+
variantId:
|
|
21309
|
+
type: string
|
|
21310
|
+
batchId:
|
|
21311
|
+
type: string
|
|
21312
|
+
SegmentEffectConfig:
|
|
21313
|
+
type: object
|
|
21314
|
+
properties:
|
|
21315
|
+
segmentId:
|
|
21316
|
+
type: string
|
|
21317
|
+
action:
|
|
21318
|
+
type: string
|
|
21319
|
+
enum:
|
|
21320
|
+
- add
|
|
21321
|
+
- remove
|
|
21322
|
+
required:
|
|
21323
|
+
- segmentId
|
|
21324
|
+
InteractionEventContext:
|
|
21325
|
+
type: object
|
|
21326
|
+
properties:
|
|
21327
|
+
eventId:
|
|
21328
|
+
type: string
|
|
21329
|
+
collectionId:
|
|
21330
|
+
type: string
|
|
21331
|
+
appId:
|
|
21332
|
+
type: string
|
|
21333
|
+
interactionId:
|
|
21334
|
+
type: string
|
|
21335
|
+
contactId:
|
|
21336
|
+
type: string
|
|
21337
|
+
userId:
|
|
21338
|
+
type: string
|
|
21339
|
+
productId:
|
|
21340
|
+
type: string
|
|
21341
|
+
proofId:
|
|
21342
|
+
type: string
|
|
21343
|
+
variantId:
|
|
21344
|
+
type: string
|
|
21345
|
+
batchId:
|
|
21346
|
+
type: string
|
|
21347
|
+
outcome:
|
|
21348
|
+
type: string
|
|
21349
|
+
eventType:
|
|
21350
|
+
type: string
|
|
21351
|
+
source:
|
|
21352
|
+
type: string
|
|
21353
|
+
timestamp:
|
|
21354
|
+
type: string
|
|
21355
|
+
metadata:
|
|
21356
|
+
type: object
|
|
21357
|
+
additionalProperties: true
|
|
21358
|
+
broadcastId:
|
|
21359
|
+
type: string
|
|
21360
|
+
journeyId:
|
|
21361
|
+
type: string
|
|
21362
|
+
required:
|
|
21363
|
+
- eventId
|
|
21364
|
+
- collectionId
|
|
21365
|
+
- appId
|
|
21366
|
+
- interactionId
|
|
21367
|
+
- contactId
|
|
21368
|
+
- userId
|
|
21369
|
+
- productId
|
|
21370
|
+
- proofId
|
|
21371
|
+
- variantId
|
|
21372
|
+
- batchId
|
|
21373
|
+
- outcome
|
|
21374
|
+
- eventType
|
|
21375
|
+
- source
|
|
21376
|
+
- timestamp
|
|
21377
|
+
- metadata
|
|
21378
|
+
- broadcastId
|
|
21379
|
+
- journeyId
|
|
21199
21380
|
Job:
|
|
21200
21381
|
type: object
|
|
21201
21382
|
properties:
|
|
@@ -22927,9 +23108,6 @@ components:
|
|
|
22927
23108
|
required:
|
|
22928
23109
|
- key
|
|
22929
23110
|
- name
|
|
22930
|
-
ProductFacetMap:
|
|
22931
|
-
type: object
|
|
22932
|
-
properties: {}
|
|
22933
23111
|
ProductQueryRequest:
|
|
22934
23112
|
type: object
|
|
22935
23113
|
properties:
|