@spree/docs 0.1.293 → 0.1.295

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.
@@ -13241,6 +13241,141 @@ components:
13241
13241
  - amount
13242
13242
  - display_amount
13243
13243
  x-typelizer: true
13244
+ ExchangeLineItem:
13245
+ type: object
13246
+ properties:
13247
+ id:
13248
+ type: string
13249
+ quantity:
13250
+ type: number
13251
+ received_quantity:
13252
+ type: number
13253
+ resellable:
13254
+ type: boolean
13255
+ original_price:
13256
+ type: string
13257
+ new_variant_price:
13258
+ type: string
13259
+ price_difference:
13260
+ type: string
13261
+ original_variant_id:
13262
+ type: string
13263
+ nullable: true
13264
+ new_variant_id:
13265
+ type: string
13266
+ nullable: true
13267
+ line_item_id:
13268
+ type: string
13269
+ nullable: true
13270
+ fulfillment_item_id:
13271
+ type: string
13272
+ nullable: true
13273
+ original_variant:
13274
+ "$ref": "#/components/schemas/Variant"
13275
+ new_variant:
13276
+ "$ref": "#/components/schemas/Variant"
13277
+ required:
13278
+ - id
13279
+ - quantity
13280
+ - received_quantity
13281
+ - resellable
13282
+ - original_price
13283
+ - new_variant_price
13284
+ - price_difference
13285
+ - original_variant_id
13286
+ - new_variant_id
13287
+ - line_item_id
13288
+ - fulfillment_item_id
13289
+ x-typelizer: true
13290
+ Exchange:
13291
+ type: object
13292
+ properties:
13293
+ id:
13294
+ type: string
13295
+ number:
13296
+ type: string
13297
+ status:
13298
+ anyOf:
13299
+ - type: string
13300
+ enum:
13301
+ - requested
13302
+ - approved
13303
+ - received
13304
+ - fulfilled
13305
+ - canceled
13306
+ - type: string
13307
+ description: The values listed are the built-in ones; extensions may add
13308
+ more.
13309
+ order_id:
13310
+ type: string
13311
+ nullable: true
13312
+ reason_id:
13313
+ type: string
13314
+ nullable: true
13315
+ price_difference:
13316
+ type: string
13317
+ display_price_difference:
13318
+ type: string
13319
+ approved_at:
13320
+ type: string
13321
+ nullable: true
13322
+ received_at:
13323
+ type: string
13324
+ nullable: true
13325
+ fulfilled_at:
13326
+ type: string
13327
+ nullable: true
13328
+ canceled_at:
13329
+ type: string
13330
+ nullable: true
13331
+ reason:
13332
+ "$ref": "#/components/schemas/ReturnReason"
13333
+ exchange_line_items:
13334
+ type: array
13335
+ items:
13336
+ "$ref": "#/components/schemas/ExchangeLineItem"
13337
+ required:
13338
+ - id
13339
+ - number
13340
+ - status
13341
+ - order_id
13342
+ - reason_id
13343
+ - price_difference
13344
+ - display_price_difference
13345
+ - approved_at
13346
+ - received_at
13347
+ - fulfilled_at
13348
+ - canceled_at
13349
+ x-typelizer: true
13350
+ Export:
13351
+ type: object
13352
+ properties:
13353
+ id:
13354
+ type: string
13355
+ number:
13356
+ type: string
13357
+ format:
13358
+ type: string
13359
+ nullable: true
13360
+ created_at:
13361
+ type: string
13362
+ updated_at:
13363
+ type: string
13364
+ type:
13365
+ type: string
13366
+ nullable: true
13367
+ user_id:
13368
+ type: string
13369
+ nullable: true
13370
+ required:
13371
+ - id
13372
+ - number
13373
+ - format
13374
+ - created_at
13375
+ - updated_at
13376
+ - type
13377
+ - user_id
13378
+ x-typelizer: true
13244
13379
  Fee:
13245
13380
  type: object
13246
13381
  properties:
@@ -13545,6 +13680,111 @@ components:
13545
13680
  - expired
13546
13681
  - active
13547
13682
  x-typelizer: true
13683
+ ImportRow:
13684
+ type: object
13685
+ properties:
13686
+ id:
13687
+ type: string
13688
+ row_number:
13689
+ type: number
13690
+ status:
13691
+ anyOf:
13692
+ - type: string
13693
+ enum:
13694
+ - pending
13695
+ - processing
13696
+ - completed
13697
+ - failed
13698
+ - type: string
13699
+ description: The values listed are the built-in ones; extensions may add
13700
+ more.
13701
+ validation_errors:
13702
+ type: string
13703
+ nullable: true
13704
+ created_at:
13705
+ type: string
13706
+ updated_at:
13707
+ type: string
13708
+ import_id:
13709
+ type: string
13710
+ nullable: true
13711
+ item_type:
13712
+ type: string
13713
+ nullable: true
13714
+ item_id:
13715
+ type: string
13716
+ nullable: true
13717
+ required:
13718
+ - id
13719
+ - row_number
13720
+ - status
13721
+ - validation_errors
13722
+ - created_at
13723
+ - updated_at
13724
+ - import_id
13725
+ - item_type
13726
+ - item_id
13727
+ x-typelizer: true
13728
+ Import:
13729
+ type: object
13730
+ properties:
13731
+ id:
13732
+ type: string
13733
+ number:
13734
+ type: string
13735
+ rows_count:
13736
+ type: number
13737
+ created_at:
13738
+ type: string
13739
+ updated_at:
13740
+ type: string
13741
+ type:
13742
+ type: string
13743
+ nullable: true
13744
+ description: 'Import type. Built-in: products, product_translations, customers,
13745
+ price_list_prices. Extensions may register more.'
13746
+ status:
13747
+ anyOf:
13748
+ - type: string
13749
+ enum:
13750
+ - pending
13751
+ - mapping
13752
+ - completed_mapping
13753
+ - processing
13754
+ - completed
13755
+ - failed
13756
+ - type: string
13757
+ description: The values listed are the built-in ones; extensions may add
13758
+ more.
13759
+ store_id:
13760
+ type: string
13761
+ nullable: true
13762
+ seller_id:
13763
+ type: string
13764
+ nullable: true
13765
+ owner_type:
13766
+ type: string
13767
+ nullable: true
13768
+ owner_id:
13769
+ type: string
13770
+ nullable: true
13771
+ user_id:
13772
+ type: string
13773
+ nullable: true
13774
+ required:
13775
+ - id
13776
+ - number
13777
+ - rows_count
13778
+ - created_at
13779
+ - updated_at
13780
+ - type
13781
+ - status
13782
+ - store_id
13783
+ - seller_id
13784
+ - owner_type
13785
+ - owner_id
13786
+ - user_id
13787
+ x-typelizer: true
13548
13788
  Invitation:
13549
13789
  type: object
13550
13790
  properties:
@@ -13917,6 +14157,32 @@ components:
13917
14157
  - xlarge_url
13918
14158
  - og_image_url
13919
14159
  x-typelizer: true
14160
+ NewsletterSubscriberRequestEvent:
14161
+ type: object
14162
+ properties:
14163
+ id:
14164
+ type: string
14165
+ email:
14166
+ type: string
14167
+ verification_token:
14168
+ type: string
14169
+ unsubscribe_token:
14170
+ type: string
14171
+ store_id:
14172
+ type: string
14173
+ nullable: true
14174
+ customer_id:
14175
+ type: string
14176
+ nullable: true
14177
+ redirect_url:
14178
+ type: string
14179
+ required:
14180
+ - id
14181
+ - email
14182
+ - unsubscribe_token
14183
+ - store_id
14184
+ - customer_id
14185
+ x-typelizer: true
13920
14186
  NewsletterSubscriber:
13921
14187
  type: object
13922
14188
  properties:
@@ -14329,6 +14595,26 @@ components:
14329
14595
  - gift_card
14330
14596
  - market
14331
14597
  x-typelizer: true
14598
+ PasswordResetRequestedEvent:
14599
+ type: object
14600
+ properties:
14601
+ id:
14602
+ type: string
14603
+ email:
14604
+ type: string
14605
+ reset_token:
14606
+ type: string
14607
+ store_id:
14608
+ type: string
14609
+ nullable: true
14610
+ redirect_url:
14611
+ type: string
14612
+ required:
14613
+ - id
14614
+ - email
14615
+ - reset_token
14616
+ - store_id
14617
+ x-typelizer: true
14332
14618
  PaymentMethod:
14333
14619
  type: object
14334
14620
  properties:
@@ -14955,6 +15241,40 @@ components:
14955
15241
  - original_price
14956
15242
  - seller_id
14957
15243
  x-typelizer: true
15244
+ ProductSubmission:
15245
+ type: object
15246
+ properties:
15247
+ id:
15248
+ type: string
15249
+ status:
15250
+ anyOf:
15251
+ - type: string
15252
+ enum:
15253
+ - pending
15254
+ - approved
15255
+ - rejected
15256
+ - withdrawn
15257
+ - type: string
15258
+ description: The values listed are the built-in ones; extensions may add
15259
+ more.
15260
+ review_note:
15261
+ type: string
15262
+ nullable: true
15263
+ reviewed_at:
15264
+ type: string
15265
+ nullable: true
15266
+ created_at:
15267
+ type: string
15268
+ product_id:
15269
+ type: string
15270
+ required:
15271
+ - id
15272
+ - status
15273
+ - review_note
15274
+ - reviewed_at
15275
+ - created_at
15276
+ - product_id
15277
+ x-typelizer: true
14958
15278
  ProductType:
14959
15279
  type: object
14960
15280
  properties:
@@ -14985,6 +15305,46 @@ components:
14985
15305
  - description
14986
15306
  - code
14987
15307
  x-typelizer: true
15308
+ PurchaseOrderEvent:
15309
+ type: object
15310
+ properties:
15311
+ id:
15312
+ type: string
15313
+ number:
15314
+ type: string
15315
+ status:
15316
+ type: string
15317
+ ordered_at:
15318
+ type: string
15319
+ nullable: true
15320
+ received_at:
15321
+ type: string
15322
+ nullable: true
15323
+ closed_short_at:
15324
+ type: string
15325
+ nullable: true
15326
+ created_at:
15327
+ type: string
15328
+ updated_at:
15329
+ type: string
15330
+ supplier_id:
15331
+ type: string
15332
+ nullable: true
15333
+ destination_location_id:
15334
+ type: string
15335
+ nullable: true
15336
+ required:
15337
+ - id
15338
+ - number
15339
+ - status
15340
+ - ordered_at
15341
+ - received_at
15342
+ - closed_short_at
15343
+ - created_at
15344
+ - updated_at
15345
+ - supplier_id
15346
+ - destination_location_id
15347
+ x-typelizer: true
14988
15348
  Refund:
14989
15349
  type: object
14990
15350
  properties:
@@ -15152,6 +15512,37 @@ components:
15152
15512
  - refunded_at
15153
15513
  - canceled_at
15154
15514
  x-typelizer: true
15515
+ SavedReportEvent:
15516
+ type: object
15517
+ properties:
15518
+ id:
15519
+ type: string
15520
+ name:
15521
+ type: string
15522
+ description:
15523
+ type: string
15524
+ nullable: true
15525
+ query:
15526
+ type: object
15527
+ seeded:
15528
+ type: boolean
15529
+ created_at:
15530
+ type: string
15531
+ updated_at:
15532
+ type: string
15533
+ user_id:
15534
+ type: string
15535
+ nullable: true
15536
+ required:
15537
+ - id
15538
+ - name
15539
+ - description
15540
+ - query
15541
+ - seeded
15542
+ - created_at
15543
+ - updated_at
15544
+ - user_id
15545
+ x-typelizer: true
15155
15546
  Seller:
15156
15547
  type: object
15157
15548
  properties:
@@ -15193,11 +15584,6 @@ components:
15193
15584
  properties:
15194
15585
  id:
15195
15586
  type: string
15196
- source:
15197
- type: string
15198
- enum:
15199
- - purchased
15200
- - uploaded
15201
15587
  status:
15202
15588
  anyOf:
15203
15589
  - type: string
@@ -15211,26 +15597,12 @@ components:
15211
15597
  carrier:
15212
15598
  type: string
15213
15599
  nullable: true
15214
- carrier_name:
15215
- type: string
15216
- nullable: true
15217
15600
  service:
15218
15601
  type: string
15219
15602
  nullable: true
15220
15603
  tracking_number:
15221
15604
  type: string
15222
15605
  nullable: true
15223
- currency:
15224
- type: string
15225
- nullable: true
15226
- format:
15227
- type: string
15228
- nullable: true
15229
- external_id:
15230
- type: string
15231
- nullable: true
15232
- metadata:
15233
- type: object
15234
15606
  refunded_at:
15235
15607
  type: string
15236
15608
  nullable: true
@@ -15243,42 +15615,19 @@ components:
15243
15615
  owner_type:
15244
15616
  type: string
15245
15617
  enum:
15246
- - Spree::Fulfillment
15247
- - Spree::Return
15248
- cost:
15249
- type: string
15250
- display_cost:
15251
- type: string
15252
- integration_id:
15253
- type: string
15254
- nullable: true
15255
- file_pending:
15256
- type: boolean
15257
- download_url:
15258
- type: string
15259
- nullable: true
15618
+ - fulfillment
15619
+ - return
15260
15620
  required:
15261
15621
  - id
15262
- - source
15263
15622
  - status
15264
15623
  - carrier
15265
- - carrier_name
15266
15624
  - service
15267
15625
  - tracking_number
15268
- - currency
15269
- - format
15270
- - external_id
15271
- - metadata
15272
15626
  - refunded_at
15273
15627
  - created_at
15274
15628
  - updated_at
15275
15629
  - owner_id
15276
15630
  - owner_type
15277
- - cost
15278
- - display_cost
15279
- - integration_id
15280
- - file_pending
15281
- - download_url
15282
15631
  x-typelizer: true
15283
15632
  State:
15284
15633
  type: object
@@ -15291,6 +15640,28 @@ components:
15291
15640
  - abbr
15292
15641
  - name
15293
15642
  x-typelizer: true
15643
+ StockLevel:
15644
+ type: object
15645
+ properties:
15646
+ id:
15647
+ type: string
15648
+ count_on_hand:
15649
+ type: number
15650
+ backorderable:
15651
+ type: boolean
15652
+ stock_location_id:
15653
+ type: string
15654
+ nullable: true
15655
+ variant_id:
15656
+ type: string
15657
+ nullable: true
15658
+ required:
15659
+ - id
15660
+ - count_on_hand
15661
+ - backorderable
15662
+ - stock_location_id
15663
+ - variant_id
15664
+ x-typelizer: true
15294
15665
  StockLocation:
15295
15666
  type: object
15296
15667
  properties:
@@ -15338,6 +15709,76 @@ components:
15338
15709
  - pickup_ready_in_minutes
15339
15710
  - pickup_instructions
15340
15711
  x-typelizer: true
15712
+ StockMovement:
15713
+ type: object
15714
+ properties:
15715
+ id:
15716
+ type: string
15717
+ quantity:
15718
+ type: number
15719
+ kind:
15720
+ type: string
15721
+ nullable: true
15722
+ enum:
15723
+ - received
15724
+ - allocated
15725
+ - shipped
15726
+ - released
15727
+ - adjusted
15728
+ reason:
15729
+ type: string
15730
+ nullable: true
15731
+ created_at:
15732
+ type: string
15733
+ updated_at:
15734
+ type: string
15735
+ stock_level_id:
15736
+ type: string
15737
+ nullable: true
15738
+ required:
15739
+ - id
15740
+ - quantity
15741
+ - kind
15742
+ - reason
15743
+ - created_at
15744
+ - updated_at
15745
+ - stock_level_id
15746
+ x-typelizer: true
15747
+ StockReceiptEvent:
15748
+ type: object
15749
+ properties:
15750
+ id:
15751
+ type: string
15752
+ number:
15753
+ type: string
15754
+ quantity_accepted_total:
15755
+ type: number
15756
+ quantity_rejected_total:
15757
+ type: number
15758
+ received_at:
15759
+ type: string
15760
+ created_at:
15761
+ type: string
15762
+ updated_at:
15763
+ type: string
15764
+ receivable_type:
15765
+ type: string
15766
+ enum:
15767
+ - purchase_order
15768
+ - stock_transfer
15769
+ receivable_id:
15770
+ type: string
15771
+ required:
15772
+ - id
15773
+ - number
15774
+ - quantity_accepted_total
15775
+ - quantity_rejected_total
15776
+ - received_at
15777
+ - created_at
15778
+ - updated_at
15779
+ - receivable_type
15780
+ - receivable_id
15781
+ x-typelizer: true
15341
15782
  StockReservation:
15342
15783
  type: object
15343
15784
  properties:
@@ -15346,6 +15787,33 @@ components:
15346
15787
  required:
15347
15788
  - id
15348
15789
  x-typelizer: true
15790
+ StockTransfer:
15791
+ type: object
15792
+ properties:
15793
+ id:
15794
+ type: string
15795
+ number:
15796
+ type: string
15797
+ reference:
15798
+ type: string
15799
+ nullable: true
15800
+ created_at:
15801
+ type: string
15802
+ updated_at:
15803
+ type: string
15804
+ source_location_id:
15805
+ type: string
15806
+ destination_location_id:
15807
+ type: string
15808
+ required:
15809
+ - id
15810
+ - number
15811
+ - reference
15812
+ - created_at
15813
+ - updated_at
15814
+ - source_location_id
15815
+ - destination_location_id
15816
+ x-typelizer: true
15349
15817
  StoreCreditEvent:
15350
15818
  type: object
15351
15819
  properties:
@@ -15403,6 +15871,23 @@ components:
15403
15871
  - display_amount_remaining
15404
15872
  - currency
15405
15873
  x-typelizer: true
15874
+ SupplierEvent:
15875
+ type: object
15876
+ properties:
15877
+ id:
15878
+ type: string
15879
+ name:
15880
+ type: string
15881
+ created_at:
15882
+ type: string
15883
+ updated_at:
15884
+ type: string
15885
+ required:
15886
+ - id
15887
+ - name
15888
+ - created_at
15889
+ - updated_at
15890
+ x-typelizer: true
15406
15891
  TaxIdentifier:
15407
15892
  type: object
15408
15893
  properties:
@@ -23,9 +23,9 @@ Every webhook delivery sends a JSON envelope with the event metadata and a `data
23
23
  | `name` | string | Event name (e.g., `order.placed`) |
24
24
  | `created_at` | string | ISO 8601 timestamp |
25
25
  | `data` | object | Serialized resource (see payloads below) |
26
- | `metadata` | object | Additional context: always the Spree version, plus event-specific keys such as `notify_customer` (fulfillment events — order events carry it in `data`) or `deprecated_alias_of` |
26
+ | `metadata` | object | Additional context: always the Spree version, plus event-specific keys such as `notify_customer` or `deprecated_alias_of` |
27
27
 
28
- Event payloads use the same [Store API V3 serializers](introduction.md) as the REST API. All `id` fields use [prefixed IDs](introduction.md) (e.g., `or_m3Rp9wXz`, `prod_86Rf07xd4z`). All monetary values are strings. All timestamps are ISO 8601.
28
+ Event payloads use the same [Store API V3 serializers](introduction.md) as the REST API, so they never carry back-office fields such as costs, internal notes or staff names. When you need those, fetch the record from the [Admin API](admin-api/introduction.md) using the `id` in the payload. All `id` fields use [prefixed IDs](introduction.md) (e.g., `or_m3Rp9wXz`, `prod_86Rf07xd4z`). All monetary values are strings. All timestamps are ISO 8601.
29
29
 
30
30
  For details on creating webhook endpoints and verifying signatures, see [Webhooks](../developer/core-concepts/webhooks.md). For the event system and the subscriber pattern, see [Events](../developer/core-concepts/events.md).
31
31
 
@@ -73,7 +73,7 @@ Events: `order.created`, `order.updated`, `order.deleted`, `order.placed`, `orde
73
73
  | `order.resend_confirmation_email` | An admin asked to send the order confirmation again |
74
74
  | `order.resend_digital_links_email` | An admin asked to send the download links again |
75
75
 
76
- The `order.placed` and `order.canceled` payloads also contain a `notify_customer` boolean. It is `false` when whoever placed or canceled the order asked for the customer not to be emailed.
76
+ The `metadata` of `order.placed` and `order.canceled` contains a `notify_customer` boolean. It is `false` when whoever placed or canceled the order asked for the customer not to be emailed.
77
77
 
78
78
  Order payloads include nested `items`, `fulfillments`, `payments`, `discounts`, `fees`, `billing_address`, `shipping_address`, `gift_card`, and `market`.
79
79
 
@@ -171,7 +171,7 @@ Carts publish their own events, separate from orders. Use them to follow carts t
171
171
 
172
172
  Events: `order_group.created`, `order_group.updated`, `order_group.deleted`, `order_group.completed`
173
173
 
174
- When one checkout creates several orders (for example, one per seller), they belong to an order group. `order_group.completed` fires once every order in the group is placed. The payload includes the group's `number`, `email`, `currency`, `total`, `item_total`, `fulfillment_status`, `payment_status`, `completed_at`, the addresses, and the nested `orders`.
174
+ When one checkout creates several orders (for example, one per seller), they belong to an order group. `order_group.completed` fires once every order in the group is placed. The payload includes the group's `number`, `email`, `currency`, `total`, `item_total`, `fulfillment_status`, `payment_status`, `completed_at`, the addresses, and the nested `orders`. Its `metadata` contains `notify_customer`, as on `order.placed`.
175
175
 
176
176
  ## Line Item Events
177
177
 
@@ -542,6 +542,21 @@ Events: `user.created`, `user.updated`, `user.deleted`, `customer.anonymized`, `
542
542
 
543
543
  Customer lifecycle events use the `user` prefix. Customer payloads include nested `addresses`, `default_billing_address`, `default_shipping_address`, `newsletter_subscriber`, and `customer_groups`.
544
544
 
545
+ `customer.password_reset_requested` carries what you need to send the reset email yourself, rather than the customer record:
546
+
547
+ ```json
548
+ {
549
+ "email": "customer@example.com",
550
+ "reset_token": "eyJfcmFpbHMiOnsi...",
551
+ "store_id": "store_UkLWZg9DAJ",
552
+ "redirect_url": "https://shop.example.com/account/reset-password"
553
+ }
554
+ ```
555
+
556
+ `redirect_url` is present only when the request included one.
557
+
558
+ > **WARNING:** A reset token lets whoever holds it take over the account. `customer.password_reset_requested` is therefore sent only to endpoints that name it — never to an endpoint subscribed to `*` or `customer.*` — and subscribing to it needs the permission to manage customers. The staff and seller equivalents are never sent to webhooks at all: Spree sends those emails itself.
559
+
545
560
  ```json
546
561
  {
547
562
  "id": "cust_k5nR8xLq",
@@ -865,7 +880,7 @@ Events: `invitation.created`, `invitation.resent`, `invitation.accepted`
865
880
 
866
881
  ## Other Events
867
882
 
868
- These resources publish events too. Their payloads use the same serializers as the matching API responses.
883
+ These resources publish events too. Back-office records (purchase orders, stock receipts, suppliers and shipping labels) send a short payload with their status, related record IDs and timestamps; fetch the rest from the Admin API.
869
884
 
870
885
  | Resource | Events |
871
886
  |---|---|
@@ -134,7 +134,13 @@ subscribes_to 'order.placed', async: false
134
134
 
135
135
  ## Publishing your own events
136
136
 
137
- Anything in your own code can announce something, and subscribers and webhooks treat it like any built-in event:
137
+ Anything in your own code can announce something, and subscribers and webhooks treat it like any built-in event. Declare the event on the model first, then publish it:
138
+
139
+ ```ruby server/config/initializers/spree_events.rb
140
+ Rails.application.config.to_prepare do
141
+ Spree::Order.publishes_event :flagged_for_review
142
+ end
143
+ ```
138
144
 
139
145
  ```ruby server/app/services/fraud_check.rb
140
146
  order.publish_event('order.flagged_for_review')
@@ -142,6 +148,10 @@ order.publish_event('order.flagged_for_review')
142
148
 
143
149
  Useful when your own domain has moments worth reacting to — a fraud check completing, an approval granted.
144
150
 
151
+ Every event Spree publishes is declared this way, which is what lets the admin dashboard's webhook picker, the [webhook events endpoint](webhooks.md#event-subscriptions) and the typed events in `@spree/sdk/webhooks` list it. Publishing an event nobody declared raises an error in development and test, so a typo is caught before it ships; in production it is logged and delivered anyway.
152
+
153
+ The payload is the model's Store API serializer, as for built-in events. To send something else, pass a serializer of your own: `publishes_event :flagged_for_review, serializer: 'MyApp::FraudFlagEventSerializer'`. Keep it to what a customer could see; back-office fields belong behind the Admin API. Facts about the event rather than the record, such as who triggered it, go in its metadata: `order.publish_event('order.flagged_for_review', nil, rule: 'velocity')`.
154
+
145
155
  ## Guidance
146
156
 
147
157
  **Listen for the specific event.** `order.placed` rather than `order.updated` with a status check.
@@ -141,6 +141,28 @@ The `subscriptions` array accepts exact event names and wildcard patterns:
141
141
  | `['order.*', 'payment.*', 'fulfillment.fulfilled']` | Multiple patterns |
142
142
  | `[]` or `['*']` | All events |
143
143
 
144
+ An event that carries a credential — `customer.password_reset_requested`, which includes a reset token — is never matched by a pattern. An endpoint receives it only when it names it, and naming it needs the permission to manage customers. Password reset requests for staff and sellers are never sent to webhooks: Spree emails those itself.
145
+
146
+ To see every event you can subscribe to, including those your installed extensions add, list them:
147
+
148
+
149
+ ```typescript Admin SDK
150
+ const { data: events } = await client.webhookEvents.list()
151
+ // [{ name: 'order.placed', group: 'order', credential: false, deprecated: false, ... }, ...]
152
+ ```
153
+
154
+ ```bash CLI
155
+ spree api get /webhook_events
156
+ ```
157
+
158
+ ```bash cURL
159
+ curl 'https://api.mystore.com/api/v3/admin/webhook_events' \
160
+ -H 'X-Spree-API-Key: sk_xxx'
161
+ ```
162
+
163
+
164
+ Each entry says whether the event carries a credential (and which permission subscribing to it needs), and whether it is a deprecated name along with the event that replaces it.
165
+
144
166
  ## Webhook Payload
145
167
 
146
168
  Each webhook delivery sends a JSON payload with the following structure. The `data` object uses the same [Store API V3 serializers](../../api-reference/introduction.md) as the REST API, so webhook payloads and API responses share the same schema:
@@ -175,7 +197,9 @@ Each webhook delivery sends a JSON payload with the following structure. The `da
175
197
  | `name` | Event name (e.g., `order.placed`) |
176
198
  | `created_at` | ISO8601 timestamp when event occurred |
177
199
  | `data` | Serialized resource data (V3 API format with [prefixed IDs](../../api-reference/introduction.md)) |
178
- | `metadata` | Additional context including Spree version |
200
+ | `metadata` | Additional context: the Spree version, plus facts about the event itself, such as `notify_customer` |
201
+
202
+ Payloads carry what a customer could see, never back-office fields such as costs, internal notes or staff names. When an integration needs those, it fetches the record from the Admin API with the `id` in the payload, using a key scoped to what it may read.
179
203
 
180
204
  For complete payload schemas for each event type, see [Webhook Events & Payloads](../../api-reference/webhooks-events.md).
181
205
 
@@ -203,29 +227,49 @@ The [Spree Storefront](https://github.com/spree/storefront) includes a ready-mad
203
227
 
204
228
  #### Any JavaScript/TypeScript framework
205
229
 
206
- Use `@spree/sdk/webhooks` for framework-agnostic verification:
230
+ Use `@spree/sdk/webhooks` for framework-agnostic verification. `constructWebhookEvent` checks the signature and returns a typed event: once you check `event.name`, `event.data` has the type of the record that event carries.
207
231
 
208
232
  ```typescript Store SDK
209
- import { verifyWebhookSignature } from '@spree/sdk/webhooks'
210
- import type { WebhookEvent } from '@spree/sdk/webhooks'
211
- import type { Order } from '@spree/sdk'
233
+ import { constructWebhookEvent, WebhookVerificationError } from '@spree/sdk/webhooks'
234
+ import { webhookEventSchemas } from '@spree/sdk/zod'
212
235
 
213
236
  // Hono, Cloudflare Workers, or any Web Fetch API-based framework
214
- app.post('/webhooks/spree', async (req, res) => {
215
- const body = await req.text()
216
- const signature = req.headers['x-spree-webhook-signature']
217
- const timestamp = req.headers['x-spree-webhook-timestamp']
237
+ app.post('/webhooks/spree', async (c) => {
238
+ let event
239
+ try {
240
+ event = constructWebhookEvent(await c.req.text(), c.req.raw.headers, process.env.SPREE_WEBHOOK_SECRET!, {
241
+ schemas: webhookEventSchemas, // optional: validate `data` at runtime
242
+ })
243
+ } catch (error) {
244
+ if (error instanceof WebhookVerificationError) return c.json({ error: 'Invalid signature' }, 401)
245
+ throw error
246
+ }
218
247
 
219
- if (!verifyWebhookSignature(body, signature, timestamp, process.env.SPREE_WEBHOOK_SECRET!)) {
220
- return res.status(401).json({ error: 'Invalid signature' })
248
+ switch (event.name) {
249
+ case 'order.placed':
250
+ await fulfil(event.data) // typed as Order
251
+ break
252
+ case 'customer.password_reset_requested':
253
+ await sendResetEmail(event.data.email, event.data.reset_token)
254
+ break
221
255
  }
222
256
 
223
- const event: WebhookEvent<Order> = JSON.parse(body)
224
- // handle event...
225
- res.json({ received: true })
257
+ return c.json({ received: true })
226
258
  })
227
259
  ```
228
260
 
261
+ Pass the body exactly as received: parsing and re-serializing it changes the bytes the signature covers. `verifyWebhookSignature` is still available when you only need the check.
262
+
263
+ Events your own code or an extension publishes can be typed too, by adding them to `WebhookEventMap`:
264
+
265
+ ```typescript
266
+ declare module '@spree/sdk/webhooks' {
267
+ interface WebhookEventMap {
268
+ 'loyalty.points_earned': { id: string; points: number }
269
+ }
270
+ }
271
+ ```
272
+
229
273
  #### Ruby
230
274
 
231
275
  ```ruby server/app/controllers/webhooks_controller.rb
@@ -324,7 +368,7 @@ SSL certificates are verified in production, and not in development — so you c
324
368
 
325
369
  ## Available Events
326
370
 
327
- Webhooks can subscribe to any event in Spree's event system. See [Webhook Events & Payloads](../../api-reference/webhooks-events.md) for the complete list.
371
+ Webhooks can subscribe to any event in Spree's event system. See [Webhook Events & Payloads](../../api-reference/webhooks-events.md) for the complete list, or [list them through the Admin API](#event-subscriptions) to include the events your extensions add.
328
372
 
329
373
  Common webhook events include:
330
374
 
@@ -42,7 +42,8 @@ Each object's fields, and the fields of the objects nested in it.
42
42
 
43
43
  ### `store`
44
44
 
45
- - `store`: `id`, `name`, `address`, `mail_from_address`, `default_currency`, `default_locale`, `url`, `support_email`, `logo_url`, `logo_width`
45
+ - `store`: `id`, `name`, `address`, `mail_from_address`, `default_currency`, `default_locale`, `url`, `support_email`, `logo_url`, `logo_width`, `branding`
46
+ - `store.branding`: `background_color`, `card_color`, `text_color`, `heading_color`, `accent_color` (empty unless the merchant set one), `link_color`, `button_color`, `button_text_color`, `button_border`, `font`, `font_family`, `heading_font_family`, `font_url` (a web font's stylesheet, empty for email-safe fonts)
46
47
 
47
48
  ### `order`
48
49
 
@@ -8,7 +8,7 @@ Every email Spree sends — order confirmations, shipping notices, password rese
8
8
 
9
9
  To change an email, you copy its template into your app and edit it. Nothing else changes: Spree still picks the recipient, the language and the sender, and delivers the email through your [SMTP provider](../providers/emails.md).
10
10
 
11
- > **NOTE:** Templates read plain data — the same fields the [Store API](../../api-reference/store-api/introduction.md) returns, plus what only an email needs — never Ruby objects. The same template can later run outside Ruby, and merchants will be able to edit templates safely from the dashboard.
11
+ > **NOTE:** Templates read plain data — the same fields the [Store API](../../api-reference/store-api/introduction.md) returns, plus what only an email needs — never Ruby objects. The same template can run outside Ruby, and merchants can edit customer emails safely from the dashboard.
12
12
 
13
13
  ## Where templates live
14
14
 
@@ -102,6 +102,73 @@ In development and test, a variable that does not exist raises an error instead
102
102
 
103
103
  To look at every email with real data, open the mailer previews at `/rails/mailers` on your Spree server.
104
104
 
105
+ ## Templates merchants edit in the dashboard
106
+
107
+ Merchants can edit the emails their customers receive in **Settings → Emails → Templates**, along with the layout and the shared blocks in `spree/shared`. An edit is saved as a draft and goes live when published; publishing renders it with the store's own data first and refuses a template that does not render. An email built from a record the store does not have yet, such as an order confirmation in a store with no orders, is checked for template syntax only. Staff, store-owner and seller emails are not editable and always render from files.
108
+
109
+ A store's published template comes first, so the lookup for an editable email is:
110
+
111
+ 1. the store's published template in the email's language
112
+ 2. the store's published template for every language
113
+ 3. your app's file
114
+ 4. Spree's file
115
+
116
+ Your file stays the default the dashboard starts from and reverts to, so overriding a template in code and letting merchants edit it work together. When a Spree upgrade or a change to your file alters a default a merchant has customized, the dashboard tells them and shows what changed.
117
+
118
+ The same operations are open to integrations through the [Admin API](../../api-reference/admin-api/introduction.md), with the `email_templates` permission:
119
+
120
+ **Admin SDK:**
121
+
122
+ ```typescript
123
+ const preview = await client.emailTemplates.preview('spree.order_mailer.confirm_email', {
124
+ body: '<mj-section><mj-column><mj-text>Thanks, {{ order.customer_name }}!</mj-text></mj-column></mj-section>',
125
+ })
126
+
127
+ await client.emailTemplates.draft.update('spree.order_mailer.confirm_email', {
128
+ body: '<mj-section><mj-column><mj-text>Thanks, {{ order.customer_name }}!</mj-text></mj-column></mj-section>',
129
+ })
130
+ await client.emailTemplates.publish('spree.order_mailer.confirm_email')
131
+ ```
132
+
133
+ **cURL:**
134
+
135
+ ```bash
136
+ curl -X PUT https://your-store.com/api/v3/admin/email_templates/spree.order_mailer.confirm_email/draft \
137
+ -H "X-Spree-Api-Key: sk_xxx" -H "Content-Type: application/json" \
138
+ -d '{"body": "<mj-section><mj-column><mj-text>Thanks, {{ order.customer_name }}!</mj-text></mj-column></mj-section>"}'
139
+
140
+ curl -X POST https://your-store.com/api/v3/admin/email_templates/spree.order_mailer.confirm_email/publication \
141
+ -H "X-Spree-Api-Key: sk_xxx"
142
+ ```
143
+
144
+
145
+ ### Branding
146
+
147
+ Merchants set the colors and font of their customer emails in **Settings → Emails**, without touching a template. Templates read them from [`store.branding`](email-variables.md#store), and Spree's layout uses them throughout. If you override the layout or a template, read colors and fonts from `store.branding` rather than writing them in, so a store's branding keeps applying.
148
+
149
+ ### Making your own customer email editable
150
+
151
+ A customer email your app adds can be edited like Spree's own. Register it with a class that builds the data its preview renders with:
152
+
153
+ ```ruby server/config/initializers/spree.rb
154
+ Rails.application.config.after_initialize do
155
+ Spree.editable_email_templates.register(
156
+ 'spree/review_mailer/request_email', kind: :email, sample: 'ReviewRequestSample'
157
+ )
158
+ end
159
+ ```
160
+
161
+ ```ruby server/app/services/review_request_sample.rb
162
+ # Previews with the store's latest completed order, or the one the merchant picks.
163
+ class ReviewRequestSample < Spree::Emails::Samples::Order
164
+ def variables
165
+ { order: super[:order], review_url: placeholder_url('reviews/new') }
166
+ end
167
+ end
168
+ ```
169
+
170
+ A sample builds from the record the merchant picked, else the store's latest one; a store with none is told there is nothing to preview with yet. Pass placeholder URLs, never real tokens, and register only emails your customers receive.
171
+
105
172
  ## Your own mailers
106
173
 
107
174
  A mailer that inherits `Spree::BaseMailer` and renders its own ERB views with `mail` keeps working. Spree wraps its HTML in the same layout as every other email, so it carries the store's logo, header and footer. The `spree/shared/mailer_hero` and `spree/shared/mailer_button` partials are still there for those views.
@@ -495,6 +495,18 @@ now returns `nil` for an ID with another model's prefix, the same as
495
495
  `find_by_prefix_id`. Call `Spree::PrefixedId.decode_prefixed_id` if you
496
496
  really need to decode any prefix.
497
497
 
498
+ ## Every event is declared
499
+
500
+ Events are now declared on the model that publishes them (`publishes_event`, `publishes_events`), and `publish_event` raises in development and test for a name nobody declared. Extensions that publish their own events need one line each — see [Publishing your own events](../core-concepts/events.md#publishing-your-own-events). In production an undeclared event is logged and still delivered.
501
+
502
+ Some payloads changed so that every webhook carries the Store API shape and nothing more:
503
+
504
+ - **`notify_customer` moved to `metadata`** on `order.placed`, `order.completed`, `order.canceled` and `order_group.completed`. Read `event.metadata['notify_customer']` in subscribers and `metadata.notify_customer` in webhook receivers.
505
+ - **`customer.anonymized`** carries the customer record; the store and the requester moved to `metadata` (`store_id`, `requested_by_id`).
506
+ - **Purchase order, stock receipt, supplier and shipping label events** carry a short payload — status, related record IDs and timestamps. Costs, notes, contact details and label files are read from the Admin API.
507
+ - **`saved_report.*`** no longer includes the author's name.
508
+ - **Subscribing an endpoint to `customer.password_reset_requested`** needs the permission to manage customers. The staff and seller reset events are still never sent to webhooks.
509
+
498
510
  ## Removed in 6.0
499
511
 
500
512
  These were deprecated in 5.x and are **gone now** — there is no bridge, so calls raise `NoMethodError`. Most were one-line delegations to a replacement that already exists.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@spree/docs",
3
- "version": "0.1.293",
3
+ "version": "0.1.295",
4
4
  "description": "Spree Commerce developer documentation for AI agents and local reference",
5
5
  "type": "module",
6
6
  "license": "CC-BY-4.0",