@fluentcommerce/fc-connect-sdk 0.1.54 → 0.1.55
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/CHANGELOG.md +12 -0
- package/dist/cjs/clients/fluent-client.js +13 -6
- package/dist/cjs/utils/pagination-helpers.js +38 -2
- package/dist/cjs/versori/fluent-versori-client.js +11 -5
- package/dist/esm/clients/fluent-client.js +13 -6
- package/dist/esm/utils/pagination-helpers.js +38 -2
- package/dist/esm/versori/fluent-versori-client.js +11 -5
- package/dist/tsconfig.esm.tsbuildinfo +1 -1
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/dist/tsconfig.types.tsbuildinfo +1 -1
- package/docs/00-START-HERE/EXPORT-VALIDATION.md +158 -158
- package/docs/00-START-HERE/cli-analyze-source-structure-guide.md +655 -655
- package/docs/00-START-HERE/cli-documentation-index.md +202 -202
- package/docs/00-START-HERE/cli-quick-reference.md +252 -252
- package/docs/00-START-HERE/decision-tree.md +552 -552
- package/docs/00-START-HERE/getting-started.md +1070 -1070
- package/docs/00-START-HERE/mapper-quick-decision-guide.md +235 -235
- package/docs/00-START-HERE/readme.md +237 -237
- package/docs/00-START-HERE/retailerid-configuration.md +404 -404
- package/docs/00-START-HERE/sdk-philosophy.md +794 -794
- package/docs/00-START-HERE/troubleshooting-quick-reference.md +1086 -1086
- package/docs/01-TEMPLATES/faq.md +686 -686
- package/docs/01-TEMPLATES/patterns/pattern-templates-guide.md +68 -68
- package/docs/01-TEMPLATES/patterns/patterns-csv-schema-validation-and-rejection-report.md +233 -233
- package/docs/01-TEMPLATES/patterns/patterns-custom-resolvers.md +407 -407
- package/docs/01-TEMPLATES/patterns/patterns-error-handling-retry.md +511 -511
- package/docs/01-TEMPLATES/patterns/patterns-field-mapping-universal.md +701 -701
- package/docs/01-TEMPLATES/patterns/patterns-large-file-splitting.md +1430 -1430
- package/docs/01-TEMPLATES/patterns/patterns-master-data-etl.md +2399 -2399
- package/docs/01-TEMPLATES/patterns/patterns-pagination-streaming.md +447 -447
- package/docs/01-TEMPLATES/patterns/patterns-state-duplicate-prevention.md +385 -385
- package/docs/01-TEMPLATES/readme.md +957 -957
- package/docs/01-TEMPLATES/standalone/standalone-asn-inbound-processing.md +1209 -1209
- package/docs/01-TEMPLATES/standalone/standalone-graphql-query-export.md +1140 -1140
- package/docs/01-TEMPLATES/standalone/standalone-graphql-to-parquet-partitioned-s3.md +432 -432
- package/docs/01-TEMPLATES/standalone/standalone-multi-channel-inventory-sync.md +1185 -1185
- package/docs/01-TEMPLATES/standalone/standalone-multi-source-aggregation.md +1462 -1462
- package/docs/01-TEMPLATES/standalone/standalone-s3-csv-batch-api.md +1390 -1390
- package/docs/01-TEMPLATES/standalone/standalone-s3-csv-inventory-to-batch.md +330 -330
- package/docs/01-TEMPLATES/standalone/standalone-scripts-guide.md +87 -87
- package/docs/01-TEMPLATES/standalone/standalone-sftp-xml-graphql.md +1444 -1444
- package/docs/01-TEMPLATES/standalone/standalone-webhook-payload-processing.md +688 -688
- package/docs/01-TEMPLATES/versori/business-examples/business-examples-dropship-order-routing.md +193 -193
- package/docs/01-TEMPLATES/versori/business-examples/business-examples-graphql-parquet-extraction.md +518 -518
- package/docs/01-TEMPLATES/versori/business-examples/business-examples-inter-location-transfers.md +2162 -2162
- package/docs/01-TEMPLATES/versori/business-examples/business-examples-pre-order-allocation.md +2226 -2226
- package/docs/01-TEMPLATES/versori/business-examples/business-scenarios-guide.md +87 -87
- package/docs/01-TEMPLATES/versori/patterns/versori-patterns-connection-validation-pattern.md +656 -656
- package/docs/01-TEMPLATES/versori/patterns/versori-patterns-dual-workflow-connector.md +835 -835
- package/docs/01-TEMPLATES/versori/patterns/versori-patterns-guide.md +108 -108
- package/docs/01-TEMPLATES/versori/patterns/versori-patterns-kv-state-management.md +1533 -1533
- package/docs/01-TEMPLATES/versori/patterns/versori-patterns-xml-response-patterns.md +1160 -1160
- package/docs/01-TEMPLATES/versori/versori-platform-guide.md +201 -201
- package/docs/01-TEMPLATES/versori/webhooks/template-webhook-asn-purchase-order.md +1906 -1906
- package/docs/01-TEMPLATES/versori/webhooks/template-webhook-dropship-routing.md +1074 -1074
- package/docs/01-TEMPLATES/versori/webhooks/template-webhook-flash-sale-reserve.md +1395 -1395
- package/docs/01-TEMPLATES/versori/webhooks/template-webhook-generic-xml-order.md +888 -888
- package/docs/01-TEMPLATES/versori/webhooks/template-webhook-payment-gateway-integration.md +2478 -2478
- package/docs/01-TEMPLATES/versori/webhooks/template-webhook-rma-returns-comprehensive.md +2240 -2240
- package/docs/01-TEMPLATES/versori/webhooks/template-webhook-xml-order-ingestion.md +2029 -2029
- package/docs/01-TEMPLATES/versori/webhooks/webhook-templates-guide.md +140 -140
- package/docs/01-TEMPLATES/versori/workflows/_examples/sample-data/inventory-mapping.json +20 -20
- package/docs/01-TEMPLATES/versori/workflows/_examples/sample-data/products_2025-01-22.csv +11 -11
- package/docs/01-TEMPLATES/versori/workflows/_examples/sample-data/sample-data-guide.md +34 -34
- package/docs/01-TEMPLATES/versori/workflows/_examples/workflow-examples-guide.md +36 -36
- package/docs/01-TEMPLATES/versori/workflows/extraction/extraction-modes-guide.md +1038 -1038
- package/docs/01-TEMPLATES/versori/workflows/extraction/extraction-workflows-guide.md +138 -138
- package/docs/01-TEMPLATES/versori/workflows/extraction/graphql-queries/graphql-extraction-guide.md +63 -63
- package/docs/01-TEMPLATES/versori/workflows/extraction/graphql-queries/template-extraction-fulfillments-to-sftp-csv.md +2062 -2062
- package/docs/01-TEMPLATES/versori/workflows/extraction/graphql-queries/template-extraction-fulfillments-to-sftp-xml.md +2294 -2294
- package/docs/01-TEMPLATES/versori/workflows/extraction/graphql-queries/template-extraction-inventory-positions-to-s3-csv.md +2461 -2461
- package/docs/01-TEMPLATES/versori/workflows/extraction/graphql-queries/template-extraction-inventory-positions-to-sftp-xml.md +2529 -2529
- package/docs/01-TEMPLATES/versori/workflows/extraction/graphql-queries/template-extraction-inventory-quantities-to-s3-csv.md +2464 -2464
- package/docs/01-TEMPLATES/versori/workflows/extraction/graphql-queries/template-extraction-inventory-quantities-to-s3-json.md +1959 -1959
- package/docs/01-TEMPLATES/versori/workflows/extraction/graphql-queries/template-extraction-orders-to-s3-csv.md +1953 -1953
- package/docs/01-TEMPLATES/versori/workflows/extraction/graphql-queries/template-extraction-orders-to-sftp-xml.md +2541 -2541
- package/docs/01-TEMPLATES/versori/workflows/extraction/graphql-queries/template-extraction-products-to-s3-json.md +2384 -2384
- package/docs/01-TEMPLATES/versori/workflows/extraction/graphql-queries/template-extraction-products-to-sftp-xml.md +2445 -2445
- package/docs/01-TEMPLATES/versori/workflows/extraction/graphql-queries/template-extraction-virtual-positions-to-s3-csv.md +2355 -2355
- package/docs/01-TEMPLATES/versori/workflows/extraction/graphql-queries/template-extraction-virtual-positions-to-s3-json.md +2042 -2042
- package/docs/01-TEMPLATES/versori/workflows/extraction/graphql-queries/template-extraction-virtual-positions-to-sftp-xml.md +2726 -2726
- package/docs/01-TEMPLATES/versori/workflows/ingestion/batch-api/batch-api-guide.md +206 -206
- package/docs/01-TEMPLATES/versori/workflows/ingestion/batch-api/template-ingestion-cycle-count-reconciliation.md +2030 -2030
- package/docs/01-TEMPLATES/versori/workflows/ingestion/batch-api/template-ingestion-multi-channel-inventory-sync.md +1882 -1882
- package/docs/01-TEMPLATES/versori/workflows/ingestion/batch-api/template-ingestion-s3-csv-inventory-batch.md +2827 -2827
- package/docs/01-TEMPLATES/versori/workflows/ingestion/batch-api/template-ingestion-s3-json-inventory-batch.md +1952 -1952
- package/docs/01-TEMPLATES/versori/workflows/ingestion/batch-api/template-ingestion-s3-xml-inventory-batch.md +3289 -3289
- package/docs/01-TEMPLATES/versori/workflows/ingestion/batch-api/template-ingestion-sftp-csv-inventory-batch.md +3064 -3064
- package/docs/01-TEMPLATES/versori/workflows/ingestion/batch-api/template-ingestion-sftp-json-inventory-batch.md +3238 -3238
- package/docs/01-TEMPLATES/versori/workflows/ingestion/batch-api/template-ingestion-sftp-xml-inventory-batch.md +2977 -2977
- package/docs/01-TEMPLATES/versori/workflows/ingestion/event-api/event-api-guide.md +321 -321
- package/docs/01-TEMPLATES/versori/workflows/ingestion/event-api/template-ingestion-payload-json-order-cancel-event.md +959 -959
- package/docs/01-TEMPLATES/versori/workflows/ingestion/event-api/template-ingestion-payload-xml-order-cancel-event.md +1170 -1170
- package/docs/01-TEMPLATES/versori/workflows/ingestion/event-api/template-ingestion-s3-csv-product-event.md +2312 -2312
- package/docs/01-TEMPLATES/versori/workflows/ingestion/event-api/template-ingestion-s3-json-product-event.md +2999 -2999
- package/docs/01-TEMPLATES/versori/workflows/ingestion/event-api/template-ingestion-s3-parquet-product-event.md +2836 -2836
- package/docs/01-TEMPLATES/versori/workflows/ingestion/event-api/template-ingestion-s3-xml-product-event.md +2395 -2395
- package/docs/01-TEMPLATES/versori/workflows/ingestion/event-api/template-ingestion-sftp-csv-product-event.md +2295 -2295
- package/docs/01-TEMPLATES/versori/workflows/ingestion/event-api/template-ingestion-sftp-json-product-event.md +2602 -2602
- package/docs/01-TEMPLATES/versori/workflows/ingestion/event-api/template-ingestion-sftp-parquet-product-event.md +2589 -2589
- package/docs/01-TEMPLATES/versori/workflows/ingestion/event-api/template-ingestion-sftp-xml-product-event.md +3578 -3578
- package/docs/01-TEMPLATES/versori/workflows/ingestion/graphql-mutations/graphql-mutations-guide.md +93 -93
- package/docs/01-TEMPLATES/versori/workflows/ingestion/graphql-mutations/template-ingestion-payload-json-order-update-graphql.md +1260 -1260
- package/docs/01-TEMPLATES/versori/workflows/ingestion/graphql-mutations/template-ingestion-payload-xml-order-update-graphql.md +1472 -1472
- package/docs/01-TEMPLATES/versori/workflows/ingestion/graphql-mutations/template-ingestion-s3-csv-control-graphql.md +2417 -2417
- package/docs/01-TEMPLATES/versori/workflows/ingestion/graphql-mutations/template-ingestion-s3-csv-location-graphql.md +2811 -2811
- package/docs/01-TEMPLATES/versori/workflows/ingestion/graphql-mutations/template-ingestion-s3-csv-price-graphql.md +2619 -2619
- package/docs/01-TEMPLATES/versori/workflows/ingestion/graphql-mutations/template-ingestion-s3-json-location-graphql.md +2807 -2807
- package/docs/01-TEMPLATES/versori/workflows/ingestion/graphql-mutations/template-ingestion-s3-xml-location-graphql.md +2373 -2373
- package/docs/01-TEMPLATES/versori/workflows/ingestion/graphql-mutations/template-ingestion-sftp-csv-control-graphql.md +2740 -2740
- package/docs/01-TEMPLATES/versori/workflows/ingestion/graphql-mutations/template-ingestion-sftp-csv-location-graphql.md +2760 -2760
- package/docs/01-TEMPLATES/versori/workflows/ingestion/graphql-mutations/template-ingestion-sftp-json-location-graphql.md +1710 -1710
- package/docs/01-TEMPLATES/versori/workflows/ingestion/ingestion-workflows-guide.md +136 -136
- package/docs/01-TEMPLATES/versori/workflows/rubix-webhooks/rubix-webhooks-guide.md +520 -520
- package/docs/01-TEMPLATES/versori/workflows/rubix-webhooks/template-webhook-rubix-fulfilment-to-sftp-xml-inline.md +1418 -1418
- package/docs/01-TEMPLATES/versori/workflows/rubix-webhooks/template-webhook-rubix-fulfilment-to-sftp-xml-universal-mapper.md +1785 -1785
- package/docs/01-TEMPLATES/versori/workflows/rubix-webhooks/template-webhook-rubix-order-attribute-update.md +824 -824
- package/docs/01-TEMPLATES/versori/workflows/workflows-overview-guide.md +646 -646
- package/docs/02-CORE-GUIDES/advanced-services/advanced-services-batch-archival.md +724 -724
- package/docs/02-CORE-GUIDES/advanced-services/advanced-services-job-tracker.md +627 -627
- package/docs/02-CORE-GUIDES/advanced-services/advanced-services-partial-batch-recovery.md +561 -561
- package/docs/02-CORE-GUIDES/advanced-services/advanced-services-quick-reference.md +367 -367
- package/docs/02-CORE-GUIDES/advanced-services/advanced-services-readme.md +407 -407
- package/docs/02-CORE-GUIDES/advanced-services/readme.md +49 -49
- package/docs/02-CORE-GUIDES/api-reference/api-reference-quick-reference.md +548 -548
- package/docs/02-CORE-GUIDES/api-reference/event-api-input-output-reference.md +702 -1171
- package/docs/02-CORE-GUIDES/api-reference/examples/client-initialization.ts +286 -286
- package/docs/02-CORE-GUIDES/api-reference/graphql-error-classification.md +337 -337
- package/docs/02-CORE-GUIDES/api-reference/modules/api-reference-01-client-api.md +399 -520
- package/docs/02-CORE-GUIDES/api-reference/modules/api-reference-03-authentication.md +199 -199
- package/docs/02-CORE-GUIDES/api-reference/modules/api-reference-04-graphql-mapping.md +925 -925
- package/docs/02-CORE-GUIDES/api-reference/modules/api-reference-05-services.md +1198 -1198
- package/docs/02-CORE-GUIDES/api-reference/modules/api-reference-06-data-sources.md +1083 -1083
- package/docs/02-CORE-GUIDES/api-reference/modules/api-reference-07-parsers.md +1097 -1097
- package/docs/02-CORE-GUIDES/api-reference/modules/api-reference-08-pagination.md +513 -513
- package/docs/02-CORE-GUIDES/api-reference/modules/api-reference-08-types.md +545 -597
- package/docs/02-CORE-GUIDES/api-reference/modules/api-reference-09-error-handling.md +527 -527
- package/docs/02-CORE-GUIDES/api-reference/modules/api-reference-09-webhook-validation.md +514 -514
- package/docs/02-CORE-GUIDES/api-reference/modules/api-reference-10-extraction.md +557 -557
- package/docs/02-CORE-GUIDES/api-reference/modules/api-reference-10-utilities.md +412 -412
- package/docs/02-CORE-GUIDES/api-reference/modules/api-reference-11-cli-tools.md +423 -423
- package/docs/02-CORE-GUIDES/api-reference/modules/api-reference-11-error-handling.md +716 -716
- package/docs/02-CORE-GUIDES/api-reference/modules/api-reference-12-analyze-source-structure.md +518 -518
- package/docs/02-CORE-GUIDES/api-reference/modules/api-reference-12-partial-responses.md +212 -212
- package/docs/02-CORE-GUIDES/api-reference/modules/api-reference-12-testing.md +300 -300
- package/docs/02-CORE-GUIDES/api-reference/modules/api-reference-13-resolver-builder.md +322 -322
- package/docs/02-CORE-GUIDES/api-reference/readme.md +279 -279
- package/docs/02-CORE-GUIDES/auto-pagination/auto-pagination-quick-reference.md +351 -351
- package/docs/02-CORE-GUIDES/auto-pagination/auto-pagination-readme.md +277 -277
- package/docs/02-CORE-GUIDES/auto-pagination/examples/auto-pagination-readme.md +178 -178
- package/docs/02-CORE-GUIDES/auto-pagination/examples/common-patterns.ts +351 -351
- package/docs/02-CORE-GUIDES/auto-pagination/examples/paginate-products.ts +384 -384
- package/docs/02-CORE-GUIDES/auto-pagination/examples/paginate-virtual-positions.ts +308 -308
- package/docs/02-CORE-GUIDES/auto-pagination/modules/auto-pagination-01-foundations.md +470 -470
- package/docs/02-CORE-GUIDES/auto-pagination/modules/auto-pagination-02-quick-start.md +713 -713
- package/docs/02-CORE-GUIDES/auto-pagination/modules/auto-pagination-03-configuration.md +754 -754
- package/docs/02-CORE-GUIDES/auto-pagination/modules/auto-pagination-04-advanced-patterns.md +732 -732
- package/docs/02-CORE-GUIDES/auto-pagination/modules/auto-pagination-05-sdk-integration.md +847 -847
- package/docs/02-CORE-GUIDES/auto-pagination/modules/auto-pagination-06-troubleshooting.md +359 -359
- package/docs/02-CORE-GUIDES/auto-pagination/modules/auto-pagination-07-api-reference.md +462 -462
- package/docs/02-CORE-GUIDES/auto-pagination/readme.md +54 -54
- package/docs/02-CORE-GUIDES/data-sources/data-sources-file-operations-error-handling.md +1487 -1487
- package/docs/02-CORE-GUIDES/data-sources/data-sources-quick-reference.md +836 -836
- package/docs/02-CORE-GUIDES/data-sources/data-sources-readme.md +276 -276
- package/docs/02-CORE-GUIDES/data-sources/data-sources-sftp-credential-access-security.md +553 -553
- package/docs/02-CORE-GUIDES/data-sources/examples/common-patterns.ts +409 -409
- package/docs/02-CORE-GUIDES/data-sources/examples/data-sources-readme.md +178 -178
- package/docs/02-CORE-GUIDES/data-sources/examples/s3-operations.ts +308 -308
- package/docs/02-CORE-GUIDES/data-sources/examples/sftp-operations.ts +371 -371
- package/docs/02-CORE-GUIDES/data-sources/modules/data-sources-01-foundations.md +735 -735
- package/docs/02-CORE-GUIDES/data-sources/modules/data-sources-02-s3-operations.md +1302 -1302
- package/docs/02-CORE-GUIDES/data-sources/modules/data-sources-03-sftp-operations.md +1379 -1379
- package/docs/02-CORE-GUIDES/data-sources/modules/data-sources-04-file-patterns.md +941 -941
- package/docs/02-CORE-GUIDES/data-sources/modules/data-sources-05-advanced-topics.md +813 -813
- package/docs/02-CORE-GUIDES/data-sources/modules/data-sources-06-integration-patterns.md +486 -486
- package/docs/02-CORE-GUIDES/data-sources/modules/data-sources-07-troubleshooting.md +387 -387
- package/docs/02-CORE-GUIDES/data-sources/modules/data-sources-08-api-reference.md +417 -417
- package/docs/02-CORE-GUIDES/data-sources/readme.md +77 -77
- package/docs/02-CORE-GUIDES/error-handling-guide.md +936 -936
- package/docs/02-CORE-GUIDES/extraction/examples/02-core-guides-extraction-readme.md +116 -116
- package/docs/02-CORE-GUIDES/extraction/examples/common-patterns.ts +428 -428
- package/docs/02-CORE-GUIDES/extraction/examples/extract-inventory-basic.ts +187 -187
- package/docs/02-CORE-GUIDES/extraction/extraction-quick-reference.md +596 -596
- package/docs/02-CORE-GUIDES/extraction/modules/02-core-guides-extraction-01-foundations.md +514 -514
- package/docs/02-CORE-GUIDES/extraction/modules/02-core-guides-extraction-02-basic-extraction.md +823 -823
- package/docs/02-CORE-GUIDES/extraction/modules/02-core-guides-extraction-03-parquet-processing.md +507 -507
- package/docs/02-CORE-GUIDES/extraction/modules/02-core-guides-extraction-04-data-enrichment.md +546 -546
- package/docs/02-CORE-GUIDES/extraction/modules/02-core-guides-extraction-05-transformation.md +494 -494
- package/docs/02-CORE-GUIDES/extraction/modules/02-core-guides-extraction-06-export-formats.md +458 -458
- package/docs/02-CORE-GUIDES/extraction/modules/02-core-guides-extraction-06-performance.md +138 -138
- package/docs/02-CORE-GUIDES/extraction/modules/02-core-guides-extraction-07-api-reference.md +148 -148
- package/docs/02-CORE-GUIDES/extraction/modules/02-core-guides-extraction-07-optimization.md +692 -692
- package/docs/02-CORE-GUIDES/extraction/modules/02-core-guides-extraction-08-extraction-orchestrator.md +1008 -1008
- package/docs/02-CORE-GUIDES/extraction/readme.md +151 -151
- package/docs/02-CORE-GUIDES/ingestion/examples/_simple-kv-store.ts +40 -40
- package/docs/02-CORE-GUIDES/ingestion/examples/error-recovery.ts +728 -728
- package/docs/02-CORE-GUIDES/ingestion/examples/event-driven.ts +501 -501
- package/docs/02-CORE-GUIDES/ingestion/examples/local-file-ingestion.ts +88 -88
- package/docs/02-CORE-GUIDES/ingestion/examples/parquet-ingestion.ts +117 -117
- package/docs/02-CORE-GUIDES/ingestion/examples/performance-optimized.ts +647 -647
- package/docs/02-CORE-GUIDES/ingestion/examples/s3-csv-ingestion.ts +169 -169
- package/docs/02-CORE-GUIDES/ingestion/examples/sftp-csv-ingestion.ts +134 -134
- package/docs/02-CORE-GUIDES/ingestion/ingestion-quick-reference.md +546 -546
- package/docs/02-CORE-GUIDES/ingestion/modules/02-core-guides-ingestion-01-introduction.md +626 -626
- package/docs/02-CORE-GUIDES/ingestion/modules/02-core-guides-ingestion-02-quick-start.md +658 -658
- package/docs/02-CORE-GUIDES/ingestion/modules/02-core-guides-ingestion-03-data-sources.md +1052 -1052
- package/docs/02-CORE-GUIDES/ingestion/modules/02-core-guides-ingestion-04-field-mapping.md +763 -763
- package/docs/02-CORE-GUIDES/ingestion/modules/02-core-guides-ingestion-05-advanced-parsers.md +676 -676
- package/docs/02-CORE-GUIDES/ingestion/modules/02-core-guides-ingestion-06-batch-api.md +1295 -1295
- package/docs/02-CORE-GUIDES/ingestion/modules/02-core-guides-ingestion-07-api-reference.md +138 -138
- package/docs/02-CORE-GUIDES/ingestion/modules/02-core-guides-ingestion-07-state-management.md +1037 -1037
- package/docs/02-CORE-GUIDES/ingestion/modules/02-core-guides-ingestion-08-performance-optimization.md +1349 -1349
- package/docs/02-CORE-GUIDES/ingestion/modules/02-core-guides-ingestion-09-best-practices.md +1893 -1893
- package/docs/02-CORE-GUIDES/ingestion/readme.md +160 -160
- package/docs/02-CORE-GUIDES/logging-guide.md +585 -585
- package/docs/02-CORE-GUIDES/mapping/error-handling-patterns.md +401 -401
- package/docs/02-CORE-GUIDES/mapping/examples/02-core-guides-mapping-readme.md +128 -128
- package/docs/02-CORE-GUIDES/mapping/examples/common-patterns.ts +273 -273
- package/docs/02-CORE-GUIDES/mapping/examples/csv-location-ingestion.json +36 -36
- package/docs/02-CORE-GUIDES/mapping/examples/csv-mapping.ts +242 -242
- package/docs/02-CORE-GUIDES/mapping/examples/graphql-to-parquet-extraction.json +36 -36
- package/docs/02-CORE-GUIDES/mapping/examples/json-mapping.ts +213 -213
- package/docs/02-CORE-GUIDES/mapping/examples/json-product-to-mutation.json +48 -48
- package/docs/02-CORE-GUIDES/mapping/examples/xml-mapping.ts +291 -291
- package/docs/02-CORE-GUIDES/mapping/examples/xml-order-to-mutation.json +45 -45
- package/docs/02-CORE-GUIDES/mapping/graphql-mutation-mapping/graphql-mutation-mapping-quick-reference.md +463 -463
- package/docs/02-CORE-GUIDES/mapping/graphql-mutation-mapping/graphql-mutation-mapping-readme.md +227 -227
- package/docs/02-CORE-GUIDES/mapping/graphql-mutation-mapping/modules/graphql-mutation-mapping-01-introduction.md +222 -222
- package/docs/02-CORE-GUIDES/mapping/graphql-mutation-mapping/modules/graphql-mutation-mapping-02-quick-start.md +351 -351
- package/docs/02-CORE-GUIDES/mapping/graphql-mutation-mapping/modules/graphql-mutation-mapping-03-schema-validation.md +569 -569
- package/docs/02-CORE-GUIDES/mapping/graphql-mutation-mapping/modules/graphql-mutation-mapping-04-mapping-patterns.md +471 -471
- package/docs/02-CORE-GUIDES/mapping/graphql-mutation-mapping/modules/graphql-mutation-mapping-05-configuration-reference.md +611 -611
- package/docs/02-CORE-GUIDES/mapping/graphql-mutation-mapping/modules/graphql-mutation-mapping-06-advanced-xpath.md +148 -148
- package/docs/02-CORE-GUIDES/mapping/graphql-mutation-mapping/modules/graphql-mutation-mapping-06-path-syntax.md +464 -464
- package/docs/02-CORE-GUIDES/mapping/graphql-mutation-mapping/modules/graphql-mutation-mapping-07-api-reference.md +94 -94
- package/docs/02-CORE-GUIDES/mapping/graphql-mutation-mapping/modules/graphql-mutation-mapping-07-array-handling.md +307 -307
- package/docs/02-CORE-GUIDES/mapping/graphql-mutation-mapping/modules/graphql-mutation-mapping-08-custom-resolvers.md +544 -544
- package/docs/02-CORE-GUIDES/mapping/graphql-mutation-mapping/modules/graphql-mutation-mapping-09-advanced-patterns.md +427 -427
- package/docs/02-CORE-GUIDES/mapping/graphql-mutation-mapping/modules/graphql-mutation-mapping-10-hooks-and-variables.md +336 -336
- package/docs/02-CORE-GUIDES/mapping/graphql-mutation-mapping/modules/graphql-mutation-mapping-11-error-handling.md +488 -488
- package/docs/02-CORE-GUIDES/mapping/graphql-mutation-mapping/modules/graphql-mutation-mapping-12-arguments-vs-nodes.md +383 -383
- package/docs/02-CORE-GUIDES/mapping/graphql-mutation-mapping/modules/graphql-mutation-mapping-13-best-practices.md +477 -477
- package/docs/02-CORE-GUIDES/mapping/graphql-mutation-mapping/readme.md +62 -62
- package/docs/02-CORE-GUIDES/mapping/mapping-format-decision-tree.md +480 -480
- package/docs/02-CORE-GUIDES/mapping/mapping-graphql-alias-batching-guide.md +820 -820
- package/docs/02-CORE-GUIDES/mapping/mapping-javascript-objects.md +2369 -2369
- package/docs/02-CORE-GUIDES/mapping/mapping-mapper-comparison-guide.md +682 -682
- package/docs/02-CORE-GUIDES/mapping/modules/02-core-guides-mapping-07-api-reference.md +1327 -1327
- package/docs/02-CORE-GUIDES/mapping/modules/02-core-guides-mapping-08-error-handling.md +1142 -1142
- package/docs/02-CORE-GUIDES/mapping/modules/mapping-04-use-cases.md +891 -891
- package/docs/02-CORE-GUIDES/mapping/modules/mapping-06-helpers-resolvers.md +1126 -1126
- package/docs/02-CORE-GUIDES/mapping/modules/mapping-06-sdk-resolvers.md +199 -199
- package/docs/02-CORE-GUIDES/mapping/modules/mapping-07-api-reference.md +1319 -1319
- package/docs/02-CORE-GUIDES/mapping/readme.md +178 -178
- package/docs/02-CORE-GUIDES/mapping/resolver-registration.md +410 -410
- package/docs/02-CORE-GUIDES/mapping/resolvers/examples/common-patterns.ts +226 -226
- package/docs/02-CORE-GUIDES/mapping/resolvers/examples/custom-resolvers.ts +227 -227
- package/docs/02-CORE-GUIDES/mapping/resolvers/examples/sdk-resolvers-usage.ts +203 -203
- package/docs/02-CORE-GUIDES/mapping/resolvers/mapping-resolvers-readme.md +274 -274
- package/docs/02-CORE-GUIDES/mapping/resolvers/mapping-resolvers-resolver-api-reference.md +679 -679
- package/docs/02-CORE-GUIDES/mapping/resolvers/mapping-resolvers-resolver-cookbook.md +826 -826
- package/docs/02-CORE-GUIDES/mapping/resolvers/mapping-resolvers-resolver-guide.md +1330 -1330
- package/docs/02-CORE-GUIDES/mapping/resolvers/mapping-resolvers-resolver-helpers-reference.md +1437 -1437
- package/docs/02-CORE-GUIDES/mapping/resolvers/mapping-resolvers-resolver-parameters-reference.md +553 -553
- package/docs/02-CORE-GUIDES/mapping/resolvers/mapping-resolvers-resolver-troubleshooting.md +854 -854
- package/docs/02-CORE-GUIDES/mapping/resolvers/readme.md +75 -75
- package/docs/02-CORE-GUIDES/parsers/examples/02-core-guides-parsers-readme.md +161 -161
- package/docs/02-CORE-GUIDES/parsers/examples/csv-parser-examples.ts +110 -110
- package/docs/02-CORE-GUIDES/parsers/examples/json-parser-examples.ts +33 -33
- package/docs/02-CORE-GUIDES/parsers/examples/parquet-parser-examples.ts +47 -47
- package/docs/02-CORE-GUIDES/parsers/examples/xml-parser-examples.ts +38 -38
- package/docs/02-CORE-GUIDES/parsers/modules/02-core-guides-parsers-01-foundations.md +355 -355
- package/docs/02-CORE-GUIDES/parsers/modules/02-core-guides-parsers-02-csv-parser.md +772 -772
- package/docs/02-CORE-GUIDES/parsers/modules/02-core-guides-parsers-03-json-parser.md +789 -789
- package/docs/02-CORE-GUIDES/parsers/modules/02-core-guides-parsers-04-xml-parser.md +857 -857
- package/docs/02-CORE-GUIDES/parsers/modules/02-core-guides-parsers-05-parquet-parser.md +603 -603
- package/docs/02-CORE-GUIDES/parsers/modules/02-core-guides-parsers-06-integration-patterns.md +702 -702
- package/docs/02-CORE-GUIDES/parsers/modules/02-core-guides-parsers-06-streaming.md +121 -121
- package/docs/02-CORE-GUIDES/parsers/modules/02-core-guides-parsers-07-api-reference.md +89 -89
- package/docs/02-CORE-GUIDES/parsers/modules/02-core-guides-parsers-07-troubleshooting.md +727 -727
- package/docs/02-CORE-GUIDES/parsers/parsers-quick-reference.md +482 -482
- package/docs/02-CORE-GUIDES/parsers/parsers-readme.md +258 -258
- package/docs/02-CORE-GUIDES/parsers/readme.md +65 -65
- package/docs/02-CORE-GUIDES/readme.md +194 -194
- package/docs/02-CORE-GUIDES/webhook-validation/examples/basic-validation.ts +108 -108
- package/docs/02-CORE-GUIDES/webhook-validation/examples/common-patterns.ts +316 -316
- package/docs/02-CORE-GUIDES/webhook-validation/examples/webhook-validation-readme.md +61 -61
- package/docs/02-CORE-GUIDES/webhook-validation/modules/webhook-validation-01-foundations.md +440 -440
- package/docs/02-CORE-GUIDES/webhook-validation/modules/webhook-validation-02-quick-start.md +525 -525
- package/docs/02-CORE-GUIDES/webhook-validation/modules/webhook-validation-03-versori-integration.md +741 -741
- package/docs/02-CORE-GUIDES/webhook-validation/modules/webhook-validation-04-platform-integration.md +629 -629
- package/docs/02-CORE-GUIDES/webhook-validation/modules/webhook-validation-05-configuration.md +535 -535
- package/docs/02-CORE-GUIDES/webhook-validation/modules/webhook-validation-06-error-handling.md +611 -611
- package/docs/02-CORE-GUIDES/webhook-validation/modules/webhook-validation-06-troubleshooting.md +124 -124
- package/docs/02-CORE-GUIDES/webhook-validation/modules/webhook-validation-07-api-reference.md +511 -511
- package/docs/02-CORE-GUIDES/webhook-validation/modules/webhook-validation-08-rubix-webhooks.md +590 -590
- package/docs/02-CORE-GUIDES/webhook-validation/modules/webhook-validation-09-rubix-event-vs-http-call.md +432 -432
- package/docs/02-CORE-GUIDES/webhook-validation/readme.md +239 -239
- package/docs/02-CORE-GUIDES/webhook-validation/webhook-validation-quick-reference.md +392 -392
- package/docs/03-PATTERN-GUIDES/connector-scenarios/connector-scenarios-quick-reference.md +498 -498
- package/docs/03-PATTERN-GUIDES/connector-scenarios/connector-scenarios-readme.md +313 -313
- package/docs/03-PATTERN-GUIDES/connector-scenarios/examples/common-patterns.ts +612 -612
- package/docs/03-PATTERN-GUIDES/connector-scenarios/examples/connector-scenarios-readme.md +253 -253
- package/docs/03-PATTERN-GUIDES/connector-scenarios/modules/connector-scenarios-01-foundations.md +452 -452
- package/docs/03-PATTERN-GUIDES/connector-scenarios/modules/connector-scenarios-02-simple-scenarios.md +681 -681
- package/docs/03-PATTERN-GUIDES/connector-scenarios/modules/connector-scenarios-03-intermediate-scenarios.md +637 -637
- package/docs/03-PATTERN-GUIDES/connector-scenarios/modules/connector-scenarios-04-advanced-scenarios.md +650 -650
- package/docs/03-PATTERN-GUIDES/connector-scenarios/modules/connector-scenarios-05-bidirectional-sync.md +233 -233
- package/docs/03-PATTERN-GUIDES/connector-scenarios/modules/connector-scenarios-06-production-patterns.md +442 -442
- package/docs/03-PATTERN-GUIDES/connector-scenarios/modules/connector-scenarios-07-reference.md +445 -445
- package/docs/03-PATTERN-GUIDES/connector-scenarios/readme.md +31 -31
- package/docs/03-PATTERN-GUIDES/enterprise-integration-patterns.md +1528 -1528
- package/docs/03-PATTERN-GUIDES/error-handling/comprehensive-error-handling-guide.md +1437 -1437
- package/docs/03-PATTERN-GUIDES/error-handling/error-handling-quick-reference.md +390 -390
- package/docs/03-PATTERN-GUIDES/error-handling/examples/common-patterns.ts +438 -438
- package/docs/03-PATTERN-GUIDES/error-handling/modules/error-handling-01-foundations.md +362 -362
- package/docs/03-PATTERN-GUIDES/error-handling/modules/error-handling-02-error-types.md +850 -850
- package/docs/03-PATTERN-GUIDES/error-handling/modules/error-handling-03-utf8-handling.md +456 -456
- package/docs/03-PATTERN-GUIDES/error-handling/modules/error-handling-04-error-scenarios.md +658 -658
- package/docs/03-PATTERN-GUIDES/error-handling/modules/error-handling-05-calling-patterns.md +671 -671
- package/docs/03-PATTERN-GUIDES/error-handling/modules/error-handling-06-retry-strategies.md +1034 -1034
- package/docs/03-PATTERN-GUIDES/error-handling/modules/error-handling-07-monitoring.md +653 -653
- package/docs/03-PATTERN-GUIDES/error-handling/modules/error-handling-08-api-reference.md +847 -847
- package/docs/03-PATTERN-GUIDES/error-handling/readme.md +36 -36
- package/docs/03-PATTERN-GUIDES/examples/__tests__/readme.md +40 -40
- package/docs/03-PATTERN-GUIDES/examples/__tests__/resolver-examples.test.js +282 -282
- package/docs/03-PATTERN-GUIDES/examples/test-data/03-pattern-guides-readme.md +110 -110
- package/docs/03-PATTERN-GUIDES/examples/test-data/canonical-inventory.json +123 -123
- package/docs/03-PATTERN-GUIDES/examples/test-data/canonical-order.json +171 -171
- package/docs/03-PATTERN-GUIDES/examples/test-data/readme.md +28 -28
- package/docs/03-PATTERN-GUIDES/extraction/extraction-readme.md +15 -15
- package/docs/03-PATTERN-GUIDES/extraction/readme.md +25 -25
- package/docs/03-PATTERN-GUIDES/file-operations/examples/common-patterns.ts +407 -407
- package/docs/03-PATTERN-GUIDES/file-operations/examples/file-operations-readme.md +142 -142
- package/docs/03-PATTERN-GUIDES/file-operations/file-operations-quick-reference.md +462 -462
- package/docs/03-PATTERN-GUIDES/file-operations/file-operations-readme.md +379 -379
- package/docs/03-PATTERN-GUIDES/file-operations/modules/file-operations-01-foundations.md +430 -430
- package/docs/03-PATTERN-GUIDES/file-operations/modules/file-operations-02-quick-start.md +484 -484
- package/docs/03-PATTERN-GUIDES/file-operations/modules/file-operations-03-s3-operations.md +507 -507
- package/docs/03-PATTERN-GUIDES/file-operations/modules/file-operations-04-sftp-operations.md +963 -963
- package/docs/03-PATTERN-GUIDES/file-operations/modules/file-operations-05-streaming-performance.md +503 -503
- package/docs/03-PATTERN-GUIDES/file-operations/modules/file-operations-06-archive-patterns.md +386 -386
- package/docs/03-PATTERN-GUIDES/file-operations/modules/file-operations-06-error-handling.md +117 -117
- package/docs/03-PATTERN-GUIDES/file-operations/modules/file-operations-07-api-reference.md +78 -78
- package/docs/03-PATTERN-GUIDES/file-operations/modules/file-operations-07-testing-troubleshooting.md +567 -567
- package/docs/03-PATTERN-GUIDES/file-operations/modules/file-operations-08-api-reference.md +1055 -1055
- package/docs/03-PATTERN-GUIDES/file-operations/readme.md +32 -32
- package/docs/03-PATTERN-GUIDES/ingestion/ingestion-readme.md +15 -15
- package/docs/03-PATTERN-GUIDES/ingestion/readme.md +25 -25
- package/docs/03-PATTERN-GUIDES/integration-patterns/examples/batch-processing.ts +130 -130
- package/docs/03-PATTERN-GUIDES/integration-patterns/examples/common-patterns.ts +360 -360
- package/docs/03-PATTERN-GUIDES/integration-patterns/examples/delta-sync.ts +130 -130
- package/docs/03-PATTERN-GUIDES/integration-patterns/examples/integration-patterns-readme.md +100 -100
- package/docs/03-PATTERN-GUIDES/integration-patterns/examples/real-time-webhook.ts +398 -398
- package/docs/03-PATTERN-GUIDES/integration-patterns/integration-patterns-quick-reference.md +962 -962
- package/docs/03-PATTERN-GUIDES/integration-patterns/integration-patterns-readme.md +134 -134
- package/docs/03-PATTERN-GUIDES/integration-patterns/modules/integration-patterns-01-real-time-processing.md +991 -991
- package/docs/03-PATTERN-GUIDES/integration-patterns/modules/integration-patterns-02-batch-processing.md +1547 -1547
- package/docs/03-PATTERN-GUIDES/integration-patterns/modules/integration-patterns-03-delta-sync.md +1108 -1108
- package/docs/03-PATTERN-GUIDES/integration-patterns/modules/integration-patterns-04-webhook-patterns.md +1181 -1181
- package/docs/03-PATTERN-GUIDES/integration-patterns/modules/integration-patterns-05-error-handling.md +1061 -1061
- package/docs/03-PATTERN-GUIDES/integration-patterns/modules/integration-patterns-06-advanced-integration-services.md +1547 -1547
- package/docs/03-PATTERN-GUIDES/integration-patterns/modules/integration-patterns-06-performance.md +109 -109
- package/docs/03-PATTERN-GUIDES/integration-patterns/modules/integration-patterns-07-api-reference.md +34 -34
- package/docs/03-PATTERN-GUIDES/integration-patterns/readme.md +30 -30
- package/docs/03-PATTERN-GUIDES/logging-minimal-mode.md +128 -128
- package/docs/03-PATTERN-GUIDES/multiple-connections/examples/common-patterns.ts +380 -380
- package/docs/03-PATTERN-GUIDES/multiple-connections/examples/multiple-connections-readme.md +139 -139
- package/docs/03-PATTERN-GUIDES/multiple-connections/examples/parallel-root-connections.ts +149 -149
- package/docs/03-PATTERN-GUIDES/multiple-connections/examples/real-world-scenarios.ts +405 -405
- package/docs/03-PATTERN-GUIDES/multiple-connections/modules/multiple-connections-01-foundations.md +378 -378
- package/docs/03-PATTERN-GUIDES/multiple-connections/modules/multiple-connections-02-quick-start.md +566 -566
- package/docs/03-PATTERN-GUIDES/multiple-connections/modules/multiple-connections-03-targeting-connections.md +659 -659
- package/docs/03-PATTERN-GUIDES/multiple-connections/modules/multiple-connections-04-parallel-queries.md +656 -656
- package/docs/03-PATTERN-GUIDES/multiple-connections/modules/multiple-connections-05-best-practices.md +624 -624
- package/docs/03-PATTERN-GUIDES/multiple-connections/modules/multiple-connections-06-api-reference.md +824 -824
- package/docs/03-PATTERN-GUIDES/multiple-connections/modules/multiple-connections-06-versori.md +119 -119
- package/docs/03-PATTERN-GUIDES/multiple-connections/modules/multiple-connections-07-api-reference.md +87 -87
- package/docs/03-PATTERN-GUIDES/multiple-connections/multiple-connections-quick-reference.md +353 -353
- package/docs/03-PATTERN-GUIDES/multiple-connections/multiple-connections-readme.md +270 -270
- package/docs/03-PATTERN-GUIDES/multiple-connections/readme.md +30 -30
- package/docs/03-PATTERN-GUIDES/pagination/pagination-readme.md +14 -14
- package/docs/03-PATTERN-GUIDES/pagination/readme.md +24 -24
- package/docs/03-PATTERN-GUIDES/parquet/examples/common-patterns.ts +180 -180
- package/docs/03-PATTERN-GUIDES/parquet/examples/read-parquet.ts +48 -48
- package/docs/03-PATTERN-GUIDES/parquet/examples/write-parquet.ts +65 -65
- package/docs/03-PATTERN-GUIDES/parquet/modules/03-pattern-guides-parquet-01-introduction.md +393 -393
- package/docs/03-PATTERN-GUIDES/parquet/modules/03-pattern-guides-parquet-02-quick-start.md +572 -572
- package/docs/03-PATTERN-GUIDES/parquet/modules/03-pattern-guides-parquet-03-reading-parquet.md +525 -525
- package/docs/03-PATTERN-GUIDES/parquet/modules/03-pattern-guides-parquet-04-writing-parquet.md +554 -554
- package/docs/03-PATTERN-GUIDES/parquet/modules/03-pattern-guides-parquet-05-graphql-extraction.md +405 -405
- package/docs/03-PATTERN-GUIDES/parquet/modules/03-pattern-guides-parquet-06-performance.md +104 -104
- package/docs/03-PATTERN-GUIDES/parquet/modules/03-pattern-guides-parquet-06-s3-integration.md +511 -511
- package/docs/03-PATTERN-GUIDES/parquet/modules/03-pattern-guides-parquet-07-api-reference.md +90 -90
- package/docs/03-PATTERN-GUIDES/parquet/modules/03-pattern-guides-parquet-07-performance-optimization.md +525 -525
- package/docs/03-PATTERN-GUIDES/parquet/modules/03-pattern-guides-parquet-08-best-practices.md +712 -712
- package/docs/03-PATTERN-GUIDES/parquet/parquet-quick-reference.md +683 -683
- package/docs/03-PATTERN-GUIDES/parquet/parquet-readme.md +248 -248
- package/docs/03-PATTERN-GUIDES/parquet/readme.md +32 -32
- package/docs/03-PATTERN-GUIDES/parsers/parsers-readme.md +12 -12
- package/docs/03-PATTERN-GUIDES/parsers/readme.md +24 -24
- package/docs/03-PATTERN-GUIDES/readme.md +159 -159
- package/docs/03-PATTERN-GUIDES/webhooks/readme.md +24 -24
- package/docs/03-PATTERN-GUIDES/webhooks/webhooks-readme.md +8 -8
- package/docs/04-REFERENCE/architecture/architecture-01-overview.md +427 -427
- package/docs/04-REFERENCE/architecture/architecture-02-client-architecture.md +424 -424
- package/docs/04-REFERENCE/architecture/architecture-03-data-flow.md +690 -690
- package/docs/04-REFERENCE/architecture/architecture-04-service-layer.md +834 -834
- package/docs/04-REFERENCE/architecture/architecture-05-integration-architecture.md +655 -655
- package/docs/04-REFERENCE/architecture/architecture-06-state-management.md +653 -653
- package/docs/04-REFERENCE/architecture/architecture-adding-new-data-sources.md +686 -686
- package/docs/04-REFERENCE/architecture/readme.md +279 -279
- package/docs/04-REFERENCE/platforms/deno/readme.md +117 -117
- package/docs/04-REFERENCE/platforms/nodejs/readme.md +146 -146
- package/docs/04-REFERENCE/platforms/readme.md +135 -135
- package/docs/04-REFERENCE/platforms/versori/modules/platforms-versori-01-introduction.md +398 -398
- package/docs/04-REFERENCE/platforms/versori/modules/platforms-versori-02-quick-start.md +560 -560
- package/docs/04-REFERENCE/platforms/versori/modules/platforms-versori-03-authentication.md +757 -757
- package/docs/04-REFERENCE/platforms/versori/modules/platforms-versori-04-workflows.md +2476 -2476
- package/docs/04-REFERENCE/platforms/versori/modules/platforms-versori-05-connections.md +1167 -1167
- package/docs/04-REFERENCE/platforms/versori/modules/platforms-versori-06-kv-storage.md +990 -990
- package/docs/04-REFERENCE/platforms/versori/modules/platforms-versori-06-state-management.md +121 -121
- package/docs/04-REFERENCE/platforms/versori/modules/platforms-versori-07-api-reference.md +68 -68
- package/docs/04-REFERENCE/platforms/versori/modules/platforms-versori-07-deployment.md +731 -731
- package/docs/04-REFERENCE/platforms/versori/modules/platforms-versori-08-best-practices.md +1111 -1111
- package/docs/04-REFERENCE/platforms/versori/modules/platforms-versori-09-signature-reference.md +766 -766
- package/docs/04-REFERENCE/platforms/versori/platforms-versori-readme.md +299 -299
- package/docs/04-REFERENCE/platforms/versori/platforms-versori-s3-sftp-configuration-guide.md +1425 -1425
- package/docs/04-REFERENCE/platforms/versori/platforms-versori-webhook-api-key-security.md +816 -816
- package/docs/04-REFERENCE/platforms/versori/platforms-versori-webhook-connection-security.md +681 -681
- package/docs/04-REFERENCE/platforms/versori/platforms-versori-workflow-task-types.md +708 -708
- package/docs/04-REFERENCE/platforms/versori/readme.md +108 -108
- package/docs/04-REFERENCE/readme.md +148 -148
- package/docs/04-REFERENCE/resolver-signature/examples/advanced-resolvers.ts +482 -482
- package/docs/04-REFERENCE/resolver-signature/examples/async-resolvers.ts +496 -496
- package/docs/04-REFERENCE/resolver-signature/examples/basic-resolvers.ts +343 -343
- package/docs/04-REFERENCE/resolver-signature/examples/resolver-signature-readme.md +188 -188
- package/docs/04-REFERENCE/resolver-signature/examples/testing-resolvers.ts +463 -463
- package/docs/04-REFERENCE/resolver-signature/modules/resolver-signature-01-foundations.md +286 -286
- package/docs/04-REFERENCE/resolver-signature/modules/resolver-signature-02-parameter-reference.md +643 -643
- package/docs/04-REFERENCE/resolver-signature/modules/resolver-signature-03-basic-examples.md +521 -521
- package/docs/04-REFERENCE/resolver-signature/modules/resolver-signature-04-advanced-patterns.md +739 -739
- package/docs/04-REFERENCE/resolver-signature/modules/resolver-signature-05-sdk-resolvers.md +531 -531
- package/docs/04-REFERENCE/resolver-signature/modules/resolver-signature-06-migration-guide.md +650 -650
- package/docs/04-REFERENCE/resolver-signature/modules/resolver-signature-06-testing.md +125 -125
- package/docs/04-REFERENCE/resolver-signature/modules/resolver-signature-07-api-reference.md +794 -794
- package/docs/04-REFERENCE/resolver-signature/readme.md +64 -64
- package/docs/04-REFERENCE/resolver-signature/resolver-signature-quick-reference.md +270 -270
- package/docs/04-REFERENCE/resolver-signature/resolver-signature-readme.md +351 -351
- package/docs/04-REFERENCE/schema/fluent-commerce-schema.json +764 -764
- package/docs/04-REFERENCE/schema/readme.md +141 -141
- package/docs/04-REFERENCE/testing/examples/04-reference-testing-readme.md +158 -158
- package/docs/04-REFERENCE/testing/examples/fluent-testing.ts +62 -62
- package/docs/04-REFERENCE/testing/examples/health-check.ts +155 -155
- package/docs/04-REFERENCE/testing/examples/integration-test.ts +119 -119
- package/docs/04-REFERENCE/testing/examples/performance-test.ts +183 -183
- package/docs/04-REFERENCE/testing/examples/s3-testing.ts +127 -127
- package/docs/04-REFERENCE/testing/modules/04-reference-testing-01-foundations.md +267 -267
- package/docs/04-REFERENCE/testing/modules/04-reference-testing-02-s3-testing.md +599 -599
- package/docs/04-REFERENCE/testing/modules/04-reference-testing-03-fluent-testing.md +589 -589
- package/docs/04-REFERENCE/testing/modules/04-reference-testing-04-integration-testing.md +699 -699
- package/docs/04-REFERENCE/testing/modules/04-reference-testing-05-debugging.md +478 -478
- package/docs/04-REFERENCE/testing/modules/04-reference-testing-06-cicd-integration.md +463 -463
- package/docs/04-REFERENCE/testing/modules/04-reference-testing-06-preflight-validation.md +131 -131
- package/docs/04-REFERENCE/testing/modules/04-reference-testing-07-best-practices.md +499 -499
- package/docs/04-REFERENCE/testing/modules/04-reference-testing-07-coverage-ci.md +165 -165
- package/docs/04-REFERENCE/testing/modules/04-reference-testing-08-api-reference.md +634 -634
- package/docs/04-REFERENCE/testing/readme.md +86 -86
- package/docs/04-REFERENCE/testing/testing-quick-reference.md +667 -667
- package/docs/04-REFERENCE/testing/testing-readme.md +286 -286
- package/docs/04-REFERENCE/troubleshooting/readme.md +144 -144
- package/docs/04-REFERENCE/troubleshooting/troubleshooting-deno-sftp-compatibility.md +392 -392
- package/docs/template-loading-matrix.md +242 -242
- package/package.json +5 -3
- package/docs/02-CORE-GUIDES/api-reference/cli-profile-integration.md +0 -377
package/docs/02-CORE-GUIDES/webhook-validation/modules/webhook-validation-03-versori-integration.md
CHANGED
|
@@ -1,741 +1,741 @@
|
|
|
1
|
-
# Module 3: Versori Integration
|
|
2
|
-
|
|
3
|
-
**Level:** Intermediate
|
|
4
|
-
**Estimated Time:** 25 minutes
|
|
5
|
-
|
|
6
|
-
## Overview
|
|
7
|
-
|
|
8
|
-
This module covers **Fluent webhook signature validation** integration with the Versori platform.
|
|
9
|
-
|
|
10
|
-
**IMPORTANT DISTINCTION**: This is about validating **Fluent Commerce webhook signatures** (cryptographic verification), which is DIFFERENT from **Versori webhook authentication** (platform-level connection-based auth). You'll learn how to validate Fluent webhooks in Versori webhook handlers, access public keys from Versori variables, handle raw body extraction, and implement production-ready Versori webhook connectors.
|
|
11
|
-
|
|
12
|
-
## Learning Objectives
|
|
13
|
-
|
|
14
|
-
By the end of this module, you will:
|
|
15
|
-
|
|
16
|
-
- Understand the difference between Versori webhook auth and Fluent signature validation
|
|
17
|
-
- Integrate Fluent signature validation in Versori webhook handlers
|
|
18
|
-
- Access public keys from Versori variables
|
|
19
|
-
- Handle raw body extraction across Versori versions
|
|
20
|
-
- Implement complete Versori webhook connectors with validation
|
|
21
|
-
- Handle Versori-specific edge cases
|
|
22
|
-
|
|
23
|
-
---
|
|
24
|
-
|
|
25
|
-
## Versori Webhook Auth vs Fluent Signature Validation
|
|
26
|
-
|
|
27
|
-
**CRITICAL DISTINCTION** - These are TWO DIFFERENT security mechanisms:
|
|
28
|
-
|
|
29
|
-
### Versori Webhook Authentication (Platform-Level)
|
|
30
|
-
|
|
31
|
-
**Purpose**: Verify webhook caller is authorized to trigger your Versori workflow
|
|
32
|
-
**Mechanism**: Connection-based authentication parameter
|
|
33
|
-
**Configuration**: `webhook('name', { connection: 'conn-name' })`
|
|
34
|
-
**Validation**: Versori platform validates BEFORE your code runs
|
|
35
|
-
**Use Case**: Restrict who can call your webhook endpoint
|
|
36
|
-
|
|
37
|
-
```typescript
|
|
38
|
-
// ✅ Versori webhook auth (platform validates caller)
|
|
39
|
-
export const myWebhook = webhook('my-webhook', {
|
|
40
|
-
connection: 'my-connection' // Platform-level auth
|
|
41
|
-
}, async (ctx) => {
|
|
42
|
-
// Only runs if caller authenticated via connection
|
|
43
|
-
});
|
|
44
|
-
```
|
|
45
|
-
|
|
46
|
-
### Fluent Signature Validation (Application-Level)
|
|
47
|
-
|
|
48
|
-
**Purpose**: Verify webhook payload came from Fluent Commerce and wasn't tampered with
|
|
49
|
-
**Mechanism**: Cryptographic signature verification (HMAC-SHA256/SHA512)
|
|
50
|
-
**Configuration**: Use `WebhookValidationService` in your code
|
|
51
|
-
**Validation**: YOUR code validates using Fluent's public key
|
|
52
|
-
**Use Case**: Verify webhook content authenticity and integrity
|
|
53
|
-
|
|
54
|
-
```typescript
|
|
55
|
-
// ✅ Fluent signature validation (your code validates content)
|
|
56
|
-
export const fluentWebhook = webhook('fluent-webhook', async (ctx) => {
|
|
57
|
-
const { log } = ctx; // Versori native log
|
|
58
|
-
const logger = toStructuredLogger(log, { service: 'webhook-validation' });
|
|
59
|
-
const validator = new WebhookValidationService({ strictValidation: true }, logger);
|
|
60
|
-
const result = await validator.validateWebhook(rawBody, headers, publicKey);
|
|
61
|
-
// Only process if Fluent signature is valid
|
|
62
|
-
});
|
|
63
|
-
```
|
|
64
|
-
|
|
65
|
-
### When to Use Which?
|
|
66
|
-
|
|
67
|
-
| Scenario | Versori Auth | Fluent Validation |
|
|
68
|
-
|----------|-------------|-------------------|
|
|
69
|
-
| **Public webhook from Fluent** | ❌ Not needed | ✅ Required |
|
|
70
|
-
| **Restricted webhook access** | ✅ Recommended | ✅ Also use |
|
|
71
|
-
| **Verify payload integrity** | ❌ Doesn't check | ✅ Required |
|
|
72
|
-
| **Multi-source webhooks** | ✅ Per-source | ✅ Per-source |
|
|
73
|
-
|
|
74
|
-
**Best Practice**: Use BOTH for Fluent webhooks:
|
|
75
|
-
1. **Versori auth** restricts WHO can call your endpoint
|
|
76
|
-
2. **Fluent validation** verifies WHAT content is authentic
|
|
77
|
-
|
|
78
|
-
---
|
|
79
|
-
|
|
80
|
-
## Versori Webhook Pattern
|
|
81
|
-
|
|
82
|
-
### Complete Versori Webhook with Validation
|
|
83
|
-
|
|
84
|
-
```typescript
|
|
85
|
-
import { webhook } from '@versori/run';
|
|
86
|
-
import {
|
|
87
|
-
WebhookValidationService,
|
|
88
|
-
createConsoleLogger,
|
|
89
|
-
toStructuredLogger,
|
|
90
|
-
} from '@fluentcommerce/fc-connect-sdk';
|
|
91
|
-
|
|
92
|
-
export const fluentWebhook = webhook('fluent-webhook', async ctx => {
|
|
93
|
-
// 1. Wrap Versori native log for SDK services
|
|
94
|
-
const { log } = ctx; // Versori native log
|
|
95
|
-
const structuredLogger = toStructuredLogger(log, {
|
|
96
|
-
service: 'webhook-validation',
|
|
97
|
-
});
|
|
98
|
-
|
|
99
|
-
// Why wrap? SDK services expect StructuredLogger interface with:
|
|
100
|
-
// - debug(), info(), warn(), error() methods
|
|
101
|
-
// - Context object with service/module/correlationId
|
|
102
|
-
// - Native Versori log provides basic methods but no context
|
|
103
|
-
|
|
104
|
-
// 2. Create validator (requires StructuredLogger)
|
|
105
|
-
const validator = new WebhookValidationService(
|
|
106
|
-
{
|
|
107
|
-
strictValidation: true,
|
|
108
|
-
},
|
|
109
|
-
structuredLogger // Must be StructuredLogger, not native log
|
|
110
|
-
);
|
|
111
|
-
|
|
112
|
-
// 3. Get public key from Versori variables
|
|
113
|
-
const publicKey = ctx.vars.FLUENT_WEBHOOK_PUBLIC_KEY;
|
|
114
|
-
if (!publicKey) {
|
|
115
|
-
structuredLogger.error('Missing public key in Versori variables');
|
|
116
|
-
return { statusCode: 500, body: 'Server configuration error' };
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
// 4. Extract raw body (version-aware)
|
|
120
|
-
const rawBody =
|
|
121
|
-
ctx.request && typeof ctx.request.text === 'function'
|
|
122
|
-
? await ctx.request.text() // Versori v0.2.29+
|
|
123
|
-
: JSON.stringify(ctx.data); // Older versions
|
|
124
|
-
|
|
125
|
-
// 5. Validate webhook
|
|
126
|
-
const req = ctx.request();
|
|
127
|
-
const headers = req?.headers || {};
|
|
128
|
-
const result = await validator.validateWebhook(rawBody, headers, publicKey);
|
|
129
|
-
|
|
130
|
-
// 6. Handle invalid signature
|
|
131
|
-
if (!result.isValid) {
|
|
132
|
-
structuredLogger.warn('Invalid webhook signature', {
|
|
133
|
-
error: result.error,
|
|
134
|
-
algorithm: result.algorithm,
|
|
135
|
-
});
|
|
136
|
-
return { statusCode: 401, body: 'Invalid signature' };
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
// 7. Process valid webhook
|
|
140
|
-
structuredLogger.info('Valid webhook received', { algorithm: result.algorithm });
|
|
141
|
-
const data = JSON.parse(rawBody);
|
|
142
|
-
|
|
143
|
-
// Your business logic here
|
|
144
|
-
await processFluentWebhook(data);
|
|
145
|
-
|
|
146
|
-
return { statusCode: 200, body: 'Success' };
|
|
147
|
-
});
|
|
148
|
-
```
|
|
149
|
-
|
|
150
|
-
---
|
|
151
|
-
|
|
152
|
-
## Versori Context Object
|
|
153
|
-
|
|
154
|
-
### Understanding ctx Object
|
|
155
|
-
|
|
156
|
-
Versori provides a rich context object to webhook handlers:
|
|
157
|
-
|
|
158
|
-
```typescript
|
|
159
|
-
interface VersoriWebhookContext {
|
|
160
|
-
// Request data
|
|
161
|
-
data: any; // Parsed JSON body
|
|
162
|
-
headers: Record<string, string>; // Request headers
|
|
163
|
-
request?: Request; // Raw Request object (v0.2.29+)
|
|
164
|
-
|
|
165
|
-
// Versori variables
|
|
166
|
-
vars: Record<string, string>; // Environment variables
|
|
167
|
-
|
|
168
|
-
// Logging
|
|
169
|
-
log: (message: string, ...args: any[]) => void;
|
|
170
|
-
|
|
171
|
-
// Other Versori utilities
|
|
172
|
-
activation: {
|
|
173
|
-
id: string;
|
|
174
|
-
workflowId: string;
|
|
175
|
-
// ... more activation metadata
|
|
176
|
-
};
|
|
177
|
-
}
|
|
178
|
-
```
|
|
179
|
-
|
|
180
|
-
### Accessing Components
|
|
181
|
-
|
|
182
|
-
```typescript
|
|
183
|
-
export const myWebhook = webhook('my-webhook', async ctx => {
|
|
184
|
-
// Parsed data (already JSON parsed by Versori)
|
|
185
|
-
const eventId = ctx.data.eventId;
|
|
186
|
-
|
|
187
|
-
// Headers
|
|
188
|
-
const req = ctx.request();
|
|
189
|
-
const signature = req?.headers['fluent-signature'] as string;
|
|
190
|
-
|
|
191
|
-
// Environment variables
|
|
192
|
-
const publicKey = ctx.vars.FLUENT_WEBHOOK_PUBLIC_KEY;
|
|
193
|
-
const env = ctx.vars.ENVIRONMENT;
|
|
194
|
-
|
|
195
|
-
// Logging
|
|
196
|
-
ctx.log('Processing webhook', eventId);
|
|
197
|
-
|
|
198
|
-
// Activation metadata
|
|
199
|
-
const workflowId = ctx.activation.workflowId;
|
|
200
|
-
});
|
|
201
|
-
```
|
|
202
|
-
|
|
203
|
-
---
|
|
204
|
-
|
|
205
|
-
## Raw Body Extraction
|
|
206
|
-
|
|
207
|
-
### Version-Aware Raw Body Extraction
|
|
208
|
-
|
|
209
|
-
Versori changed how raw bodies are accessed in version 0.2.29:
|
|
210
|
-
|
|
211
|
-
```typescript
|
|
212
|
-
/**
|
|
213
|
-
* Extract raw request body - handles both old and new Versori versions
|
|
214
|
-
*/
|
|
215
|
-
async function getRawBody(ctx: any): Promise<string> {
|
|
216
|
-
// Versori v0.2.29+ provides Request object with text() method
|
|
217
|
-
if (ctx.request && typeof ctx.request.text === 'function') {
|
|
218
|
-
return await ctx.request.text();
|
|
219
|
-
}
|
|
220
|
-
|
|
221
|
-
// Older Versori versions - reconstruct from parsed data
|
|
222
|
-
if (ctx.data) {
|
|
223
|
-
return JSON.stringify(ctx.data);
|
|
224
|
-
}
|
|
225
|
-
|
|
226
|
-
throw new Error('Unable to extract raw body from context');
|
|
227
|
-
}
|
|
228
|
-
|
|
229
|
-
// Usage in webhook
|
|
230
|
-
export const myWebhook = webhook('my-webhook', async ctx => {
|
|
231
|
-
const rawBody = await getRawBody(ctx); // Works across versions
|
|
232
|
-
// ... validate with rawBody
|
|
233
|
-
});
|
|
234
|
-
```
|
|
235
|
-
|
|
236
|
-
### Why Raw Body Matters
|
|
237
|
-
|
|
238
|
-
```typescript
|
|
239
|
-
// ❌ WRONG - Using pre-parsed data
|
|
240
|
-
const rawBody = JSON.stringify(ctx.data);
|
|
241
|
-
// Problem: JSON.stringify may reorder keys, change whitespace
|
|
242
|
-
// This breaks signature verification
|
|
243
|
-
|
|
244
|
-
// ✅ CORRECT - Use original raw body
|
|
245
|
-
const rawBody = await ctx.request.text();
|
|
246
|
-
// This preserves exact byte sequence for signature verification
|
|
247
|
-
```
|
|
248
|
-
|
|
249
|
-
**Best Practice**: Always use `ctx.request.text()` when available (Versori v0.2.29+). For older versions, `JSON.stringify(ctx.data)` is acceptable but may have edge cases.
|
|
250
|
-
|
|
251
|
-
---
|
|
252
|
-
|
|
253
|
-
## Versori Variables for Public Keys
|
|
254
|
-
|
|
255
|
-
### Setting Up Versori Variables
|
|
256
|
-
|
|
257
|
-
In Versori platform UI:
|
|
258
|
-
|
|
259
|
-
1. Navigate to your workflow
|
|
260
|
-
2. Go to **Variables** tab
|
|
261
|
-
3. Add variable: `FLUENT_WEBHOOK_PUBLIC_KEY`
|
|
262
|
-
4. Set value to your Fluent Commerce public key
|
|
263
|
-
5. Mark as **Secret** for security
|
|
264
|
-
|
|
265
|
-
```typescript
|
|
266
|
-
// Access in webhook handler
|
|
267
|
-
const publicKey = ctx.vars.FLUENT_WEBHOOK_PUBLIC_KEY;
|
|
268
|
-
```
|
|
269
|
-
|
|
270
|
-
### Multiple Environment Keys
|
|
271
|
-
|
|
272
|
-
For workflows that handle multiple environments (prod/sandbox):
|
|
273
|
-
|
|
274
|
-
```typescript
|
|
275
|
-
// Set multiple variables in Versori
|
|
276
|
-
// FLUENT_WEBHOOK_PUBLIC_KEY_PROD
|
|
277
|
-
// FLUENT_WEBHOOK_PUBLIC_KEY_SANDBOX
|
|
278
|
-
|
|
279
|
-
// Access based on environment
|
|
280
|
-
export const myWebhook = webhook('my-webhook', async ctx => {
|
|
281
|
-
const env = ctx.vars.ENVIRONMENT || 'production';
|
|
282
|
-
|
|
283
|
-
const publicKey =
|
|
284
|
-
env === 'production'
|
|
285
|
-
? ctx.vars.FLUENT_WEBHOOK_PUBLIC_KEY_PROD
|
|
286
|
-
: ctx.vars.FLUENT_WEBHOOK_PUBLIC_KEY_SANDBOX;
|
|
287
|
-
|
|
288
|
-
if (!publicKey) {
|
|
289
|
-
throw new Error(`Missing public key for environment: ${env}`);
|
|
290
|
-
}
|
|
291
|
-
|
|
292
|
-
// ... validate with environment-specific key
|
|
293
|
-
});
|
|
294
|
-
```
|
|
295
|
-
|
|
296
|
-
---
|
|
297
|
-
|
|
298
|
-
## Complete Versori Connector Example
|
|
299
|
-
|
|
300
|
-
### Production-Ready Webhook Connector
|
|
301
|
-
|
|
302
|
-
```typescript
|
|
303
|
-
import { webhook, fn } from '@versori/run';
|
|
304
|
-
import {
|
|
305
|
-
WebhookValidationService,
|
|
306
|
-
WebhookValidationResult,
|
|
307
|
-
createConsoleLogger,
|
|
308
|
-
toStructuredLogger,
|
|
309
|
-
} from '@fluentcommerce/fc-connect-sdk';
|
|
310
|
-
|
|
311
|
-
/**
|
|
312
|
-
* Fluent Commerce webhook handler with validation
|
|
313
|
-
*/
|
|
314
|
-
export const fluentOrderWebhook = webhook('fluent-order-webhook')
|
|
315
|
-
.then(
|
|
316
|
-
fn('validate', async ctx => {
|
|
317
|
-
// Initialize services
|
|
318
|
-
const { log } = ctx; // Versori native log
|
|
319
|
-
const logger = toStructuredLogger(log, {
|
|
320
|
-
service: 'webhook-validator',
|
|
321
|
-
});
|
|
322
|
-
|
|
323
|
-
// Get configuration
|
|
324
|
-
const publicKey = ctx.vars.FLUENT_WEBHOOK_PUBLIC_KEY;
|
|
325
|
-
if (!publicKey) {
|
|
326
|
-
throw new Error('FLUENT_WEBHOOK_PUBLIC_KEY not configured in Versori variables');
|
|
327
|
-
}
|
|
328
|
-
|
|
329
|
-
// Extract raw body
|
|
330
|
-
const rawBody =
|
|
331
|
-
ctx.request && typeof ctx.request.text === 'function'
|
|
332
|
-
? await ctx.request.text()
|
|
333
|
-
: JSON.stringify(ctx.data);
|
|
334
|
-
|
|
335
|
-
// Create validator
|
|
336
|
-
const validator = new WebhookValidationService(
|
|
337
|
-
{
|
|
338
|
-
strictValidation: true,
|
|
339
|
-
},
|
|
340
|
-
logger
|
|
341
|
-
);
|
|
342
|
-
|
|
343
|
-
// Validate signature
|
|
344
|
-
const req = ctx.request();
|
|
345
|
-
const headers = req?.headers || {};
|
|
346
|
-
const result: WebhookValidationResult = await validator.validateWebhook(
|
|
347
|
-
rawBody,
|
|
348
|
-
headers,
|
|
349
|
-
publicKey
|
|
350
|
-
);
|
|
351
|
-
|
|
352
|
-
// Handle validation failure
|
|
353
|
-
if (!result.isValid) {
|
|
354
|
-
logger.error('Webhook validation failed', {
|
|
355
|
-
error: result.error,
|
|
356
|
-
algorithm: result.algorithm,
|
|
357
|
-
workflowId: ctx.activation.workflowId,
|
|
358
|
-
headers: Object.keys(headers),
|
|
359
|
-
});
|
|
360
|
-
|
|
361
|
-
throw new Error(`Invalid webhook signature: ${result.error}`);
|
|
362
|
-
}
|
|
363
|
-
|
|
364
|
-
// Log successful validation
|
|
365
|
-
logger.info('Webhook validated successfully', {
|
|
366
|
-
algorithm: result.algorithm,
|
|
367
|
-
timestamp: result.timestamp,
|
|
368
|
-
workflowId: ctx.activation.workflowId,
|
|
369
|
-
});
|
|
370
|
-
|
|
371
|
-
// Parse and return data
|
|
372
|
-
const webhookData = JSON.parse(rawBody);
|
|
373
|
-
return {
|
|
374
|
-
...ctx,
|
|
375
|
-
data: webhookData,
|
|
376
|
-
validationResult: result,
|
|
377
|
-
};
|
|
378
|
-
})
|
|
379
|
-
)
|
|
380
|
-
.then(
|
|
381
|
-
fn('processOrder', async ctx => {
|
|
382
|
-
// Access validated webhook data
|
|
383
|
-
const { eventId, entityType, entityId, data } = ctx.data;
|
|
384
|
-
|
|
385
|
-
ctx.log('Processing validated webhook', {
|
|
386
|
-
eventId,
|
|
387
|
-
entityType,
|
|
388
|
-
entityId,
|
|
389
|
-
});
|
|
390
|
-
|
|
391
|
-
// Your business logic here
|
|
392
|
-
if (entityType === 'ORDER') {
|
|
393
|
-
await processOrder(data);
|
|
394
|
-
}
|
|
395
|
-
|
|
396
|
-
return {
|
|
397
|
-
success: true,
|
|
398
|
-
eventId,
|
|
399
|
-
processedAt: new Date().toISOString(),
|
|
400
|
-
};
|
|
401
|
-
})
|
|
402
|
-
)
|
|
403
|
-
.catch(({ error, data }) => {
|
|
404
|
-
// Handle validation or processing errors
|
|
405
|
-
console.error('Webhook processing failed', { error });
|
|
406
|
-
|
|
407
|
-
return {
|
|
408
|
-
success: false,
|
|
409
|
-
error: error.message,
|
|
410
|
-
timestamp: new Date().toISOString(),
|
|
411
|
-
};
|
|
412
|
-
});
|
|
413
|
-
|
|
414
|
-
// Helper function
|
|
415
|
-
async function processOrder(orderData: any): Promise<void> {
|
|
416
|
-
// Your order processing logic
|
|
417
|
-
console.log('Processing order:', orderData.ref);
|
|
418
|
-
}
|
|
419
|
-
```
|
|
420
|
-
|
|
421
|
-
---
|
|
422
|
-
|
|
423
|
-
## Versori Response Patterns
|
|
424
|
-
|
|
425
|
-
### Standard JSON Response
|
|
426
|
-
|
|
427
|
-
```typescript
|
|
428
|
-
export const webhook1 = webhook('webhook1', async ctx => {
|
|
429
|
-
const req = ctx.request();
|
|
430
|
-
const headers = req?.headers || {};
|
|
431
|
-
const result = await validator.validateWebhook(rawBody, headers, publicKey);
|
|
432
|
-
|
|
433
|
-
if (!result.isValid) {
|
|
434
|
-
return {
|
|
435
|
-
statusCode: 401,
|
|
436
|
-
body: { error: 'Invalid signature', details: result.error },
|
|
437
|
-
};
|
|
438
|
-
}
|
|
439
|
-
|
|
440
|
-
return {
|
|
441
|
-
statusCode: 200,
|
|
442
|
-
body: { success: true, eventId: ctx.data.eventId },
|
|
443
|
-
};
|
|
444
|
-
});
|
|
445
|
-
```
|
|
446
|
-
|
|
447
|
-
### Custom Response Headers
|
|
448
|
-
|
|
449
|
-
```typescript
|
|
450
|
-
export const webhook2 = webhook('webhook2', async ctx => {
|
|
451
|
-
const req = ctx.request();
|
|
452
|
-
const headers = req?.headers || {};
|
|
453
|
-
const result = await validator.validateWebhook(rawBody, headers, publicKey);
|
|
454
|
-
|
|
455
|
-
if (!result.isValid) {
|
|
456
|
-
return {
|
|
457
|
-
statusCode: 401,
|
|
458
|
-
headers: { 'X-Validation-Error': result.error },
|
|
459
|
-
body: 'Unauthorized',
|
|
460
|
-
};
|
|
461
|
-
}
|
|
462
|
-
|
|
463
|
-
return {
|
|
464
|
-
statusCode: 200,
|
|
465
|
-
headers: { 'X-Validation-Algorithm': result.algorithm },
|
|
466
|
-
body: 'Success',
|
|
467
|
-
};
|
|
468
|
-
});
|
|
469
|
-
```
|
|
470
|
-
|
|
471
|
-
### Non-JSON Response (XML, plain text, etc.)
|
|
472
|
-
|
|
473
|
-
For non-JSON responses, use custom response handlers:
|
|
474
|
-
|
|
475
|
-
```typescript
|
|
476
|
-
import { webhook } from '@versori/run';
|
|
477
|
-
import {
|
|
478
|
-
WebhookValidationService,
|
|
479
|
-
createConsoleLogger,
|
|
480
|
-
toStructuredLogger,
|
|
481
|
-
} from '@fluentcommerce/fc-connect-sdk';
|
|
482
|
-
|
|
483
|
-
export const xmlWebhook = webhook('xml-webhook', {
|
|
484
|
-
response: {
|
|
485
|
-
mode: 'sync',
|
|
486
|
-
onSuccess: ctx =>
|
|
487
|
-
new Response(ctx.data, {
|
|
488
|
-
status: 200,
|
|
489
|
-
headers: { 'Content-Type': 'application/xml; charset=utf-8' },
|
|
490
|
-
}),
|
|
491
|
-
onError: ctx =>
|
|
492
|
-
new Response(ctx.data, {
|
|
493
|
-
status: 401,
|
|
494
|
-
headers: { 'Content-Type': 'application/xml; charset=utf-8' },
|
|
495
|
-
}),
|
|
496
|
-
},
|
|
497
|
-
})
|
|
498
|
-
.then(async ctx => {
|
|
499
|
-
// Create validator
|
|
500
|
-
const { log } = ctx; // Versori native log
|
|
501
|
-
const logger = toStructuredLogger(log, {
|
|
502
|
-
service: 'webhook-validation',
|
|
503
|
-
});
|
|
504
|
-
const validator = new WebhookValidationService({ strictValidation: true }, logger);
|
|
505
|
-
|
|
506
|
-
// Extract raw body and public key
|
|
507
|
-
const rawBody = await ctx.request.text(); // Versori v0.2.29+
|
|
508
|
-
const publicKey = ctx.vars.FLUENT_WEBHOOK_PUBLIC_KEY;
|
|
509
|
-
|
|
510
|
-
// Validate
|
|
511
|
-
const req = ctx.request();
|
|
512
|
-
const headers = req?.headers || {};
|
|
513
|
-
const result = await validator.validateWebhook(rawBody, headers, publicKey);
|
|
514
|
-
|
|
515
|
-
if (!result.isValid) {
|
|
516
|
-
throw new Error('Invalid signature');
|
|
517
|
-
}
|
|
518
|
-
|
|
519
|
-
// Return XML response
|
|
520
|
-
return '<?xml version="1.0"?><response><status>success</status></response>';
|
|
521
|
-
})
|
|
522
|
-
.catch(({ error }) => {
|
|
523
|
-
// Return XML error
|
|
524
|
-
return `<?xml version="1.0"?><response><status>error</status><message>${error.message}</message></response>`;
|
|
525
|
-
});
|
|
526
|
-
```
|
|
527
|
-
|
|
528
|
-
See [Versori Webhook Response Patterns](../../../04-REFERENCE/platforms/versori/modules/platforms-versori-04-workflows.md#critical-non-json-response-handlers-xml-html-csv) for complete documentation.
|
|
529
|
-
|
|
530
|
-
---
|
|
531
|
-
|
|
532
|
-
## Testing Versori Webhooks
|
|
533
|
-
|
|
534
|
-
### Local Testing Setup
|
|
535
|
-
|
|
536
|
-
```typescript
|
|
537
|
-
// test-webhook-locally.ts
|
|
538
|
-
import {
|
|
539
|
-
WebhookValidationService,
|
|
540
|
-
createConsoleLogger,
|
|
541
|
-
toStructuredLogger,
|
|
542
|
-
} from '@fluentcommerce/fc-connect-sdk';
|
|
543
|
-
|
|
544
|
-
async function testVersoriWebhook() {
|
|
545
|
-
// Simulate Versori context
|
|
546
|
-
const mockContext = {
|
|
547
|
-
data: {
|
|
548
|
-
eventId: 'evt_test_123',
|
|
549
|
-
entityType: 'ORDER',
|
|
550
|
-
entityId: 'order_456',
|
|
551
|
-
},
|
|
552
|
-
headers: {
|
|
553
|
-
'fluent-signature': 'YOUR_TEST_SIGNATURE',
|
|
554
|
-
'content-type': 'application/json',
|
|
555
|
-
},
|
|
556
|
-
vars: {
|
|
557
|
-
FLUENT_WEBHOOK_PUBLIC_KEY: process.env.FLUENT_WEBHOOK_PUBLIC_KEY!,
|
|
558
|
-
},
|
|
559
|
-
request: {
|
|
560
|
-
text: async () =>
|
|
561
|
-
JSON.stringify({
|
|
562
|
-
eventId: 'evt_test_123',
|
|
563
|
-
entityType: 'ORDER',
|
|
564
|
-
entityId: 'order_456',
|
|
565
|
-
}),
|
|
566
|
-
},
|
|
567
|
-
log: console.log,
|
|
568
|
-
activation: {
|
|
569
|
-
id: 'test-activation',
|
|
570
|
-
workflowId: 'test-workflow',
|
|
571
|
-
},
|
|
572
|
-
};
|
|
573
|
-
|
|
574
|
-
// Test validation
|
|
575
|
-
const logger = toStructuredLogger(mockContext.log, {
|
|
576
|
-
service: 'test',
|
|
577
|
-
});
|
|
578
|
-
const validator = new WebhookValidationService({ strictValidation: true }, logger);
|
|
579
|
-
|
|
580
|
-
const rawBody = await mockContext.request.text();
|
|
581
|
-
const result = await validator.validateWebhook(
|
|
582
|
-
rawBody,
|
|
583
|
-
mockContext.headers,
|
|
584
|
-
mockContext.vars.FLUENT_WEBHOOK_PUBLIC_KEY
|
|
585
|
-
);
|
|
586
|
-
|
|
587
|
-
console.log('Test result:', result);
|
|
588
|
-
}
|
|
589
|
-
|
|
590
|
-
testVersoriWebhook();
|
|
591
|
-
```
|
|
592
|
-
|
|
593
|
-
### Versori Deploy & Test
|
|
594
|
-
|
|
595
|
-
```bash
|
|
596
|
-
# Deploy to Versori
|
|
597
|
-
npm run deploy
|
|
598
|
-
|
|
599
|
-
# Get webhook URL from Versori dashboard
|
|
600
|
-
# Example: https://yourspace.versori.run/webhooks/fluent-webhook
|
|
601
|
-
|
|
602
|
-
# Test with curl (get real signature from Fluent test environment)
|
|
603
|
-
curl -X POST https://yourspace.versori.run/webhooks/fluent-webhook \
|
|
604
|
-
-H "Content-Type: application/json" \
|
|
605
|
-
-H "fluent-signature: REAL_SIGNATURE_FROM_FLUENT" \
|
|
606
|
-
-d '{"eventId":"evt_123","entityType":"ORDER","entityId":"order_456"}'
|
|
607
|
-
|
|
608
|
-
# Check Versori logs
|
|
609
|
-
npm run logs
|
|
610
|
-
```
|
|
611
|
-
|
|
612
|
-
---
|
|
613
|
-
|
|
614
|
-
## Versori-Specific Edge Cases
|
|
615
|
-
|
|
616
|
-
### Edge Case 1: Missing Request Object (Old Versori)
|
|
617
|
-
|
|
618
|
-
```typescript
|
|
619
|
-
// Handle gracefully when ctx.request is undefined
|
|
620
|
-
const rawBody =
|
|
621
|
-
ctx.request && typeof ctx.request.text === 'function'
|
|
622
|
-
? await ctx.request.text()
|
|
623
|
-
: JSON.stringify(ctx.data); // Fallback for old versions
|
|
624
|
-
```
|
|
625
|
-
|
|
626
|
-
### Edge Case 2: Headers Case Sensitivity
|
|
627
|
-
|
|
628
|
-
```typescript
|
|
629
|
-
// Versori may normalize header names to lowercase
|
|
630
|
-
const req = ctx.request();
|
|
631
|
-
const headers = req?.headers || {};
|
|
632
|
-
const signature =
|
|
633
|
-
headers['fluent-signature'] || headers['Fluent-Signature'] || headers['FLUENT-SIGNATURE'];
|
|
634
|
-
|
|
635
|
-
// SDK handles this internally, but be aware for custom logic
|
|
636
|
-
```
|
|
637
|
-
|
|
638
|
-
### Edge Case 3: Empty Body
|
|
639
|
-
|
|
640
|
-
```typescript
|
|
641
|
-
// Handle webhooks with empty or null body
|
|
642
|
-
const rawBody =
|
|
643
|
-
ctx.request && typeof ctx.request.text === 'function'
|
|
644
|
-
? await ctx.request.text()
|
|
645
|
-
: ctx.data
|
|
646
|
-
? JSON.stringify(ctx.data)
|
|
647
|
-
: '{}';
|
|
648
|
-
|
|
649
|
-
if (!rawBody || rawBody === '{}') {
|
|
650
|
-
logger.warn('Received webhook with empty body');
|
|
651
|
-
return { statusCode: 400, body: 'Empty webhook body' };
|
|
652
|
-
}
|
|
653
|
-
```
|
|
654
|
-
|
|
655
|
-
---
|
|
656
|
-
|
|
657
|
-
## Key Takeaways
|
|
658
|
-
|
|
659
|
-
- **Use ctx.request.text()** for raw body in Versori v0.2.29+ (fallback to JSON.stringify for older versions)
|
|
660
|
-
- **Store public keys** in Versori variables as secrets
|
|
661
|
-
- **Version-aware code** handles both old and new Versori platforms
|
|
662
|
-
- **Validate before processing** - always check signature before business logic
|
|
663
|
-
- **Use workflow functions** - chain validation → processing for clean separation
|
|
664
|
-
- **Handle errors gracefully** - return proper HTTP status codes for validation failures
|
|
665
|
-
|
|
666
|
-
---
|
|
667
|
-
|
|
668
|
-
## Practice Exercise
|
|
669
|
-
|
|
670
|
-
Create a Versori webhook connector that:
|
|
671
|
-
|
|
672
|
-
1. Validates incoming Fluent Commerce webhooks
|
|
673
|
-
2. Handles both SHA512 and MD5 signatures
|
|
674
|
-
3. Logs validation results
|
|
675
|
-
4. Processes valid webhooks
|
|
676
|
-
5. Returns appropriate HTTP status codes
|
|
677
|
-
|
|
678
|
-
<details>
|
|
679
|
-
<summary>Solution</summary>
|
|
680
|
-
|
|
681
|
-
```typescript
|
|
682
|
-
import { webhook, fn } from '@versori/run';
|
|
683
|
-
import {
|
|
684
|
-
WebhookValidationService,
|
|
685
|
-
createConsoleLogger,
|
|
686
|
-
toStructuredLogger,
|
|
687
|
-
} from '@fluentcommerce/fc-connect-sdk';
|
|
688
|
-
|
|
689
|
-
export const practiceWebhook = webhook('practice-webhook')
|
|
690
|
-
.then(
|
|
691
|
-
fn('validate', async ctx => {
|
|
692
|
-
const { log } = ctx; // Versori native log
|
|
693
|
-
const logger = toStructuredLogger(log, {
|
|
694
|
-
service: 'practice',
|
|
695
|
-
});
|
|
696
|
-
const validator = new WebhookValidationService({ strictValidation: true }, logger);
|
|
697
|
-
|
|
698
|
-
const publicKey = ctx.vars.FLUENT_WEBHOOK_PUBLIC_KEY;
|
|
699
|
-
if (!publicKey) throw new Error('Missing public key');
|
|
700
|
-
|
|
701
|
-
const rawBody = await ctx.request.text(); // Versori v0.2.29+
|
|
702
|
-
const req = ctx.request();
|
|
703
|
-
const headers = req?.headers || {};
|
|
704
|
-
const result = await validator.validateWebhook(rawBody, headers, publicKey);
|
|
705
|
-
|
|
706
|
-
if (!result.isValid) {
|
|
707
|
-
throw new Error(`Validation failed: ${result.error}`);
|
|
708
|
-
}
|
|
709
|
-
|
|
710
|
-
logger.info('Validated with algorithm', { algorithm: result.algorithm });
|
|
711
|
-
return { ...ctx, data: JSON.parse(rawBody) };
|
|
712
|
-
})
|
|
713
|
-
)
|
|
714
|
-
.then(
|
|
715
|
-
fn('process', async ctx => {
|
|
716
|
-
ctx.log('Processing webhook', ctx.data.eventId);
|
|
717
|
-
// Your processing logic
|
|
718
|
-
return { success: true, eventId: ctx.data.eventId };
|
|
719
|
-
})
|
|
720
|
-
)
|
|
721
|
-
.catch(({ error }) => ({
|
|
722
|
-
statusCode: error.message.includes('Validation failed') ? 401 : 500,
|
|
723
|
-
body: { error: error.message },
|
|
724
|
-
}));
|
|
725
|
-
```
|
|
726
|
-
|
|
727
|
-
</details>
|
|
728
|
-
|
|
729
|
-
---
|
|
730
|
-
|
|
731
|
-
## Next Steps
|
|
732
|
-
|
|
733
|
-
Continue to [Module 4: Platform Integration](./webhook-validation-04-platform-integration.md) to learn how to integrate webhook validation with Express.js, Next.js, AWS Lambda, and other platforms.
|
|
734
|
-
|
|
735
|
-
---
|
|
736
|
-
|
|
737
|
-
## Further Reading
|
|
738
|
-
|
|
739
|
-
- [Versori Webhook Response Patterns](../../../04-REFERENCE/platforms/versori/modules/platforms-versori-04-workflows.md#critical-non-json-response-handlers-xml-html-csv) - Complete guide to Versori response handling
|
|
740
|
-
- [Module 5: Configuration](./webhook-validation-05-configuration.md) - Advanced configuration options
|
|
741
|
-
- [Module 6: Error Handling](./webhook-validation-06-error-handling.md) - Comprehensive error handling
|
|
1
|
+
# Module 3: Versori Integration
|
|
2
|
+
|
|
3
|
+
**Level:** Intermediate
|
|
4
|
+
**Estimated Time:** 25 minutes
|
|
5
|
+
|
|
6
|
+
## Overview
|
|
7
|
+
|
|
8
|
+
This module covers **Fluent webhook signature validation** integration with the Versori platform.
|
|
9
|
+
|
|
10
|
+
**IMPORTANT DISTINCTION**: This is about validating **Fluent Commerce webhook signatures** (cryptographic verification), which is DIFFERENT from **Versori webhook authentication** (platform-level connection-based auth). You'll learn how to validate Fluent webhooks in Versori webhook handlers, access public keys from Versori variables, handle raw body extraction, and implement production-ready Versori webhook connectors.
|
|
11
|
+
|
|
12
|
+
## Learning Objectives
|
|
13
|
+
|
|
14
|
+
By the end of this module, you will:
|
|
15
|
+
|
|
16
|
+
- Understand the difference between Versori webhook auth and Fluent signature validation
|
|
17
|
+
- Integrate Fluent signature validation in Versori webhook handlers
|
|
18
|
+
- Access public keys from Versori variables
|
|
19
|
+
- Handle raw body extraction across Versori versions
|
|
20
|
+
- Implement complete Versori webhook connectors with validation
|
|
21
|
+
- Handle Versori-specific edge cases
|
|
22
|
+
|
|
23
|
+
---
|
|
24
|
+
|
|
25
|
+
## Versori Webhook Auth vs Fluent Signature Validation
|
|
26
|
+
|
|
27
|
+
**CRITICAL DISTINCTION** - These are TWO DIFFERENT security mechanisms:
|
|
28
|
+
|
|
29
|
+
### Versori Webhook Authentication (Platform-Level)
|
|
30
|
+
|
|
31
|
+
**Purpose**: Verify webhook caller is authorized to trigger your Versori workflow
|
|
32
|
+
**Mechanism**: Connection-based authentication parameter
|
|
33
|
+
**Configuration**: `webhook('name', { connection: 'conn-name' })`
|
|
34
|
+
**Validation**: Versori platform validates BEFORE your code runs
|
|
35
|
+
**Use Case**: Restrict who can call your webhook endpoint
|
|
36
|
+
|
|
37
|
+
```typescript
|
|
38
|
+
// ✅ Versori webhook auth (platform validates caller)
|
|
39
|
+
export const myWebhook = webhook('my-webhook', {
|
|
40
|
+
connection: 'my-connection' // Platform-level auth
|
|
41
|
+
}, async (ctx) => {
|
|
42
|
+
// Only runs if caller authenticated via connection
|
|
43
|
+
});
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
### Fluent Signature Validation (Application-Level)
|
|
47
|
+
|
|
48
|
+
**Purpose**: Verify webhook payload came from Fluent Commerce and wasn't tampered with
|
|
49
|
+
**Mechanism**: Cryptographic signature verification (HMAC-SHA256/SHA512)
|
|
50
|
+
**Configuration**: Use `WebhookValidationService` in your code
|
|
51
|
+
**Validation**: YOUR code validates using Fluent's public key
|
|
52
|
+
**Use Case**: Verify webhook content authenticity and integrity
|
|
53
|
+
|
|
54
|
+
```typescript
|
|
55
|
+
// ✅ Fluent signature validation (your code validates content)
|
|
56
|
+
export const fluentWebhook = webhook('fluent-webhook', async (ctx) => {
|
|
57
|
+
const { log } = ctx; // Versori native log
|
|
58
|
+
const logger = toStructuredLogger(log, { service: 'webhook-validation' });
|
|
59
|
+
const validator = new WebhookValidationService({ strictValidation: true }, logger);
|
|
60
|
+
const result = await validator.validateWebhook(rawBody, headers, publicKey);
|
|
61
|
+
// Only process if Fluent signature is valid
|
|
62
|
+
});
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
### When to Use Which?
|
|
66
|
+
|
|
67
|
+
| Scenario | Versori Auth | Fluent Validation |
|
|
68
|
+
|----------|-------------|-------------------|
|
|
69
|
+
| **Public webhook from Fluent** | ❌ Not needed | ✅ Required |
|
|
70
|
+
| **Restricted webhook access** | ✅ Recommended | ✅ Also use |
|
|
71
|
+
| **Verify payload integrity** | ❌ Doesn't check | ✅ Required |
|
|
72
|
+
| **Multi-source webhooks** | ✅ Per-source | ✅ Per-source |
|
|
73
|
+
|
|
74
|
+
**Best Practice**: Use BOTH for Fluent webhooks:
|
|
75
|
+
1. **Versori auth** restricts WHO can call your endpoint
|
|
76
|
+
2. **Fluent validation** verifies WHAT content is authentic
|
|
77
|
+
|
|
78
|
+
---
|
|
79
|
+
|
|
80
|
+
## Versori Webhook Pattern
|
|
81
|
+
|
|
82
|
+
### Complete Versori Webhook with Validation
|
|
83
|
+
|
|
84
|
+
```typescript
|
|
85
|
+
import { webhook } from '@versori/run';
|
|
86
|
+
import {
|
|
87
|
+
WebhookValidationService,
|
|
88
|
+
createConsoleLogger,
|
|
89
|
+
toStructuredLogger,
|
|
90
|
+
} from '@fluentcommerce/fc-connect-sdk';
|
|
91
|
+
|
|
92
|
+
export const fluentWebhook = webhook('fluent-webhook', async ctx => {
|
|
93
|
+
// 1. Wrap Versori native log for SDK services
|
|
94
|
+
const { log } = ctx; // Versori native log
|
|
95
|
+
const structuredLogger = toStructuredLogger(log, {
|
|
96
|
+
service: 'webhook-validation',
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
// Why wrap? SDK services expect StructuredLogger interface with:
|
|
100
|
+
// - debug(), info(), warn(), error() methods
|
|
101
|
+
// - Context object with service/module/correlationId
|
|
102
|
+
// - Native Versori log provides basic methods but no context
|
|
103
|
+
|
|
104
|
+
// 2. Create validator (requires StructuredLogger)
|
|
105
|
+
const validator = new WebhookValidationService(
|
|
106
|
+
{
|
|
107
|
+
strictValidation: true,
|
|
108
|
+
},
|
|
109
|
+
structuredLogger // Must be StructuredLogger, not native log
|
|
110
|
+
);
|
|
111
|
+
|
|
112
|
+
// 3. Get public key from Versori variables
|
|
113
|
+
const publicKey = ctx.vars.FLUENT_WEBHOOK_PUBLIC_KEY;
|
|
114
|
+
if (!publicKey) {
|
|
115
|
+
structuredLogger.error('Missing public key in Versori variables');
|
|
116
|
+
return { statusCode: 500, body: 'Server configuration error' };
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
// 4. Extract raw body (version-aware)
|
|
120
|
+
const rawBody =
|
|
121
|
+
ctx.request && typeof ctx.request.text === 'function'
|
|
122
|
+
? await ctx.request.text() // Versori v0.2.29+
|
|
123
|
+
: JSON.stringify(ctx.data); // Older versions
|
|
124
|
+
|
|
125
|
+
// 5. Validate webhook
|
|
126
|
+
const req = ctx.request();
|
|
127
|
+
const headers = req?.headers || {};
|
|
128
|
+
const result = await validator.validateWebhook(rawBody, headers, publicKey);
|
|
129
|
+
|
|
130
|
+
// 6. Handle invalid signature
|
|
131
|
+
if (!result.isValid) {
|
|
132
|
+
structuredLogger.warn('Invalid webhook signature', {
|
|
133
|
+
error: result.error,
|
|
134
|
+
algorithm: result.algorithm,
|
|
135
|
+
});
|
|
136
|
+
return { statusCode: 401, body: 'Invalid signature' };
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
// 7. Process valid webhook
|
|
140
|
+
structuredLogger.info('Valid webhook received', { algorithm: result.algorithm });
|
|
141
|
+
const data = JSON.parse(rawBody);
|
|
142
|
+
|
|
143
|
+
// Your business logic here
|
|
144
|
+
await processFluentWebhook(data);
|
|
145
|
+
|
|
146
|
+
return { statusCode: 200, body: 'Success' };
|
|
147
|
+
});
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
---
|
|
151
|
+
|
|
152
|
+
## Versori Context Object
|
|
153
|
+
|
|
154
|
+
### Understanding ctx Object
|
|
155
|
+
|
|
156
|
+
Versori provides a rich context object to webhook handlers:
|
|
157
|
+
|
|
158
|
+
```typescript
|
|
159
|
+
interface VersoriWebhookContext {
|
|
160
|
+
// Request data
|
|
161
|
+
data: any; // Parsed JSON body
|
|
162
|
+
headers: Record<string, string>; // Request headers
|
|
163
|
+
request?: Request; // Raw Request object (v0.2.29+)
|
|
164
|
+
|
|
165
|
+
// Versori variables
|
|
166
|
+
vars: Record<string, string>; // Environment variables
|
|
167
|
+
|
|
168
|
+
// Logging
|
|
169
|
+
log: (message: string, ...args: any[]) => void;
|
|
170
|
+
|
|
171
|
+
// Other Versori utilities
|
|
172
|
+
activation: {
|
|
173
|
+
id: string;
|
|
174
|
+
workflowId: string;
|
|
175
|
+
// ... more activation metadata
|
|
176
|
+
};
|
|
177
|
+
}
|
|
178
|
+
```
|
|
179
|
+
|
|
180
|
+
### Accessing Components
|
|
181
|
+
|
|
182
|
+
```typescript
|
|
183
|
+
export const myWebhook = webhook('my-webhook', async ctx => {
|
|
184
|
+
// Parsed data (already JSON parsed by Versori)
|
|
185
|
+
const eventId = ctx.data.eventId;
|
|
186
|
+
|
|
187
|
+
// Headers
|
|
188
|
+
const req = ctx.request();
|
|
189
|
+
const signature = req?.headers['fluent-signature'] as string;
|
|
190
|
+
|
|
191
|
+
// Environment variables
|
|
192
|
+
const publicKey = ctx.vars.FLUENT_WEBHOOK_PUBLIC_KEY;
|
|
193
|
+
const env = ctx.vars.ENVIRONMENT;
|
|
194
|
+
|
|
195
|
+
// Logging
|
|
196
|
+
ctx.log('Processing webhook', eventId);
|
|
197
|
+
|
|
198
|
+
// Activation metadata
|
|
199
|
+
const workflowId = ctx.activation.workflowId;
|
|
200
|
+
});
|
|
201
|
+
```
|
|
202
|
+
|
|
203
|
+
---
|
|
204
|
+
|
|
205
|
+
## Raw Body Extraction
|
|
206
|
+
|
|
207
|
+
### Version-Aware Raw Body Extraction
|
|
208
|
+
|
|
209
|
+
Versori changed how raw bodies are accessed in version 0.2.29:
|
|
210
|
+
|
|
211
|
+
```typescript
|
|
212
|
+
/**
|
|
213
|
+
* Extract raw request body - handles both old and new Versori versions
|
|
214
|
+
*/
|
|
215
|
+
async function getRawBody(ctx: any): Promise<string> {
|
|
216
|
+
// Versori v0.2.29+ provides Request object with text() method
|
|
217
|
+
if (ctx.request && typeof ctx.request.text === 'function') {
|
|
218
|
+
return await ctx.request.text();
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
// Older Versori versions - reconstruct from parsed data
|
|
222
|
+
if (ctx.data) {
|
|
223
|
+
return JSON.stringify(ctx.data);
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
throw new Error('Unable to extract raw body from context');
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
// Usage in webhook
|
|
230
|
+
export const myWebhook = webhook('my-webhook', async ctx => {
|
|
231
|
+
const rawBody = await getRawBody(ctx); // Works across versions
|
|
232
|
+
// ... validate with rawBody
|
|
233
|
+
});
|
|
234
|
+
```
|
|
235
|
+
|
|
236
|
+
### Why Raw Body Matters
|
|
237
|
+
|
|
238
|
+
```typescript
|
|
239
|
+
// ❌ WRONG - Using pre-parsed data
|
|
240
|
+
const rawBody = JSON.stringify(ctx.data);
|
|
241
|
+
// Problem: JSON.stringify may reorder keys, change whitespace
|
|
242
|
+
// This breaks signature verification
|
|
243
|
+
|
|
244
|
+
// ✅ CORRECT - Use original raw body
|
|
245
|
+
const rawBody = await ctx.request.text();
|
|
246
|
+
// This preserves exact byte sequence for signature verification
|
|
247
|
+
```
|
|
248
|
+
|
|
249
|
+
**Best Practice**: Always use `ctx.request.text()` when available (Versori v0.2.29+). For older versions, `JSON.stringify(ctx.data)` is acceptable but may have edge cases.
|
|
250
|
+
|
|
251
|
+
---
|
|
252
|
+
|
|
253
|
+
## Versori Variables for Public Keys
|
|
254
|
+
|
|
255
|
+
### Setting Up Versori Variables
|
|
256
|
+
|
|
257
|
+
In Versori platform UI:
|
|
258
|
+
|
|
259
|
+
1. Navigate to your workflow
|
|
260
|
+
2. Go to **Variables** tab
|
|
261
|
+
3. Add variable: `FLUENT_WEBHOOK_PUBLIC_KEY`
|
|
262
|
+
4. Set value to your Fluent Commerce public key
|
|
263
|
+
5. Mark as **Secret** for security
|
|
264
|
+
|
|
265
|
+
```typescript
|
|
266
|
+
// Access in webhook handler
|
|
267
|
+
const publicKey = ctx.vars.FLUENT_WEBHOOK_PUBLIC_KEY;
|
|
268
|
+
```
|
|
269
|
+
|
|
270
|
+
### Multiple Environment Keys
|
|
271
|
+
|
|
272
|
+
For workflows that handle multiple environments (prod/sandbox):
|
|
273
|
+
|
|
274
|
+
```typescript
|
|
275
|
+
// Set multiple variables in Versori
|
|
276
|
+
// FLUENT_WEBHOOK_PUBLIC_KEY_PROD
|
|
277
|
+
// FLUENT_WEBHOOK_PUBLIC_KEY_SANDBOX
|
|
278
|
+
|
|
279
|
+
// Access based on environment
|
|
280
|
+
export const myWebhook = webhook('my-webhook', async ctx => {
|
|
281
|
+
const env = ctx.vars.ENVIRONMENT || 'production';
|
|
282
|
+
|
|
283
|
+
const publicKey =
|
|
284
|
+
env === 'production'
|
|
285
|
+
? ctx.vars.FLUENT_WEBHOOK_PUBLIC_KEY_PROD
|
|
286
|
+
: ctx.vars.FLUENT_WEBHOOK_PUBLIC_KEY_SANDBOX;
|
|
287
|
+
|
|
288
|
+
if (!publicKey) {
|
|
289
|
+
throw new Error(`Missing public key for environment: ${env}`);
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
// ... validate with environment-specific key
|
|
293
|
+
});
|
|
294
|
+
```
|
|
295
|
+
|
|
296
|
+
---
|
|
297
|
+
|
|
298
|
+
## Complete Versori Connector Example
|
|
299
|
+
|
|
300
|
+
### Production-Ready Webhook Connector
|
|
301
|
+
|
|
302
|
+
```typescript
|
|
303
|
+
import { webhook, fn } from '@versori/run';
|
|
304
|
+
import {
|
|
305
|
+
WebhookValidationService,
|
|
306
|
+
WebhookValidationResult,
|
|
307
|
+
createConsoleLogger,
|
|
308
|
+
toStructuredLogger,
|
|
309
|
+
} from '@fluentcommerce/fc-connect-sdk';
|
|
310
|
+
|
|
311
|
+
/**
|
|
312
|
+
* Fluent Commerce webhook handler with validation
|
|
313
|
+
*/
|
|
314
|
+
export const fluentOrderWebhook = webhook('fluent-order-webhook')
|
|
315
|
+
.then(
|
|
316
|
+
fn('validate', async ctx => {
|
|
317
|
+
// Initialize services
|
|
318
|
+
const { log } = ctx; // Versori native log
|
|
319
|
+
const logger = toStructuredLogger(log, {
|
|
320
|
+
service: 'webhook-validator',
|
|
321
|
+
});
|
|
322
|
+
|
|
323
|
+
// Get configuration
|
|
324
|
+
const publicKey = ctx.vars.FLUENT_WEBHOOK_PUBLIC_KEY;
|
|
325
|
+
if (!publicKey) {
|
|
326
|
+
throw new Error('FLUENT_WEBHOOK_PUBLIC_KEY not configured in Versori variables');
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
// Extract raw body
|
|
330
|
+
const rawBody =
|
|
331
|
+
ctx.request && typeof ctx.request.text === 'function'
|
|
332
|
+
? await ctx.request.text()
|
|
333
|
+
: JSON.stringify(ctx.data);
|
|
334
|
+
|
|
335
|
+
// Create validator
|
|
336
|
+
const validator = new WebhookValidationService(
|
|
337
|
+
{
|
|
338
|
+
strictValidation: true,
|
|
339
|
+
},
|
|
340
|
+
logger
|
|
341
|
+
);
|
|
342
|
+
|
|
343
|
+
// Validate signature
|
|
344
|
+
const req = ctx.request();
|
|
345
|
+
const headers = req?.headers || {};
|
|
346
|
+
const result: WebhookValidationResult = await validator.validateWebhook(
|
|
347
|
+
rawBody,
|
|
348
|
+
headers,
|
|
349
|
+
publicKey
|
|
350
|
+
);
|
|
351
|
+
|
|
352
|
+
// Handle validation failure
|
|
353
|
+
if (!result.isValid) {
|
|
354
|
+
logger.error('Webhook validation failed', {
|
|
355
|
+
error: result.error,
|
|
356
|
+
algorithm: result.algorithm,
|
|
357
|
+
workflowId: ctx.activation.workflowId,
|
|
358
|
+
headers: Object.keys(headers),
|
|
359
|
+
});
|
|
360
|
+
|
|
361
|
+
throw new Error(`Invalid webhook signature: ${result.error}`);
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
// Log successful validation
|
|
365
|
+
logger.info('Webhook validated successfully', {
|
|
366
|
+
algorithm: result.algorithm,
|
|
367
|
+
timestamp: result.timestamp,
|
|
368
|
+
workflowId: ctx.activation.workflowId,
|
|
369
|
+
});
|
|
370
|
+
|
|
371
|
+
// Parse and return data
|
|
372
|
+
const webhookData = JSON.parse(rawBody);
|
|
373
|
+
return {
|
|
374
|
+
...ctx,
|
|
375
|
+
data: webhookData,
|
|
376
|
+
validationResult: result,
|
|
377
|
+
};
|
|
378
|
+
})
|
|
379
|
+
)
|
|
380
|
+
.then(
|
|
381
|
+
fn('processOrder', async ctx => {
|
|
382
|
+
// Access validated webhook data
|
|
383
|
+
const { eventId, entityType, entityId, data } = ctx.data;
|
|
384
|
+
|
|
385
|
+
ctx.log('Processing validated webhook', {
|
|
386
|
+
eventId,
|
|
387
|
+
entityType,
|
|
388
|
+
entityId,
|
|
389
|
+
});
|
|
390
|
+
|
|
391
|
+
// Your business logic here
|
|
392
|
+
if (entityType === 'ORDER') {
|
|
393
|
+
await processOrder(data);
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
return {
|
|
397
|
+
success: true,
|
|
398
|
+
eventId,
|
|
399
|
+
processedAt: new Date().toISOString(),
|
|
400
|
+
};
|
|
401
|
+
})
|
|
402
|
+
)
|
|
403
|
+
.catch(({ error, data }) => {
|
|
404
|
+
// Handle validation or processing errors
|
|
405
|
+
console.error('Webhook processing failed', { error });
|
|
406
|
+
|
|
407
|
+
return {
|
|
408
|
+
success: false,
|
|
409
|
+
error: error.message,
|
|
410
|
+
timestamp: new Date().toISOString(),
|
|
411
|
+
};
|
|
412
|
+
});
|
|
413
|
+
|
|
414
|
+
// Helper function
|
|
415
|
+
async function processOrder(orderData: any): Promise<void> {
|
|
416
|
+
// Your order processing logic
|
|
417
|
+
console.log('Processing order:', orderData.ref);
|
|
418
|
+
}
|
|
419
|
+
```
|
|
420
|
+
|
|
421
|
+
---
|
|
422
|
+
|
|
423
|
+
## Versori Response Patterns
|
|
424
|
+
|
|
425
|
+
### Standard JSON Response
|
|
426
|
+
|
|
427
|
+
```typescript
|
|
428
|
+
export const webhook1 = webhook('webhook1', async ctx => {
|
|
429
|
+
const req = ctx.request();
|
|
430
|
+
const headers = req?.headers || {};
|
|
431
|
+
const result = await validator.validateWebhook(rawBody, headers, publicKey);
|
|
432
|
+
|
|
433
|
+
if (!result.isValid) {
|
|
434
|
+
return {
|
|
435
|
+
statusCode: 401,
|
|
436
|
+
body: { error: 'Invalid signature', details: result.error },
|
|
437
|
+
};
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
return {
|
|
441
|
+
statusCode: 200,
|
|
442
|
+
body: { success: true, eventId: ctx.data.eventId },
|
|
443
|
+
};
|
|
444
|
+
});
|
|
445
|
+
```
|
|
446
|
+
|
|
447
|
+
### Custom Response Headers
|
|
448
|
+
|
|
449
|
+
```typescript
|
|
450
|
+
export const webhook2 = webhook('webhook2', async ctx => {
|
|
451
|
+
const req = ctx.request();
|
|
452
|
+
const headers = req?.headers || {};
|
|
453
|
+
const result = await validator.validateWebhook(rawBody, headers, publicKey);
|
|
454
|
+
|
|
455
|
+
if (!result.isValid) {
|
|
456
|
+
return {
|
|
457
|
+
statusCode: 401,
|
|
458
|
+
headers: { 'X-Validation-Error': result.error },
|
|
459
|
+
body: 'Unauthorized',
|
|
460
|
+
};
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
return {
|
|
464
|
+
statusCode: 200,
|
|
465
|
+
headers: { 'X-Validation-Algorithm': result.algorithm },
|
|
466
|
+
body: 'Success',
|
|
467
|
+
};
|
|
468
|
+
});
|
|
469
|
+
```
|
|
470
|
+
|
|
471
|
+
### Non-JSON Response (XML, plain text, etc.)
|
|
472
|
+
|
|
473
|
+
For non-JSON responses, use custom response handlers:
|
|
474
|
+
|
|
475
|
+
```typescript
|
|
476
|
+
import { webhook } from '@versori/run';
|
|
477
|
+
import {
|
|
478
|
+
WebhookValidationService,
|
|
479
|
+
createConsoleLogger,
|
|
480
|
+
toStructuredLogger,
|
|
481
|
+
} from '@fluentcommerce/fc-connect-sdk';
|
|
482
|
+
|
|
483
|
+
export const xmlWebhook = webhook('xml-webhook', {
|
|
484
|
+
response: {
|
|
485
|
+
mode: 'sync',
|
|
486
|
+
onSuccess: ctx =>
|
|
487
|
+
new Response(ctx.data, {
|
|
488
|
+
status: 200,
|
|
489
|
+
headers: { 'Content-Type': 'application/xml; charset=utf-8' },
|
|
490
|
+
}),
|
|
491
|
+
onError: ctx =>
|
|
492
|
+
new Response(ctx.data, {
|
|
493
|
+
status: 401,
|
|
494
|
+
headers: { 'Content-Type': 'application/xml; charset=utf-8' },
|
|
495
|
+
}),
|
|
496
|
+
},
|
|
497
|
+
})
|
|
498
|
+
.then(async ctx => {
|
|
499
|
+
// Create validator
|
|
500
|
+
const { log } = ctx; // Versori native log
|
|
501
|
+
const logger = toStructuredLogger(log, {
|
|
502
|
+
service: 'webhook-validation',
|
|
503
|
+
});
|
|
504
|
+
const validator = new WebhookValidationService({ strictValidation: true }, logger);
|
|
505
|
+
|
|
506
|
+
// Extract raw body and public key
|
|
507
|
+
const rawBody = await ctx.request.text(); // Versori v0.2.29+
|
|
508
|
+
const publicKey = ctx.vars.FLUENT_WEBHOOK_PUBLIC_KEY;
|
|
509
|
+
|
|
510
|
+
// Validate
|
|
511
|
+
const req = ctx.request();
|
|
512
|
+
const headers = req?.headers || {};
|
|
513
|
+
const result = await validator.validateWebhook(rawBody, headers, publicKey);
|
|
514
|
+
|
|
515
|
+
if (!result.isValid) {
|
|
516
|
+
throw new Error('Invalid signature');
|
|
517
|
+
}
|
|
518
|
+
|
|
519
|
+
// Return XML response
|
|
520
|
+
return '<?xml version="1.0"?><response><status>success</status></response>';
|
|
521
|
+
})
|
|
522
|
+
.catch(({ error }) => {
|
|
523
|
+
// Return XML error
|
|
524
|
+
return `<?xml version="1.0"?><response><status>error</status><message>${error.message}</message></response>`;
|
|
525
|
+
});
|
|
526
|
+
```
|
|
527
|
+
|
|
528
|
+
See [Versori Webhook Response Patterns](../../../04-REFERENCE/platforms/versori/modules/platforms-versori-04-workflows.md#critical-non-json-response-handlers-xml-html-csv) for complete documentation.
|
|
529
|
+
|
|
530
|
+
---
|
|
531
|
+
|
|
532
|
+
## Testing Versori Webhooks
|
|
533
|
+
|
|
534
|
+
### Local Testing Setup
|
|
535
|
+
|
|
536
|
+
```typescript
|
|
537
|
+
// test-webhook-locally.ts
|
|
538
|
+
import {
|
|
539
|
+
WebhookValidationService,
|
|
540
|
+
createConsoleLogger,
|
|
541
|
+
toStructuredLogger,
|
|
542
|
+
} from '@fluentcommerce/fc-connect-sdk';
|
|
543
|
+
|
|
544
|
+
async function testVersoriWebhook() {
|
|
545
|
+
// Simulate Versori context
|
|
546
|
+
const mockContext = {
|
|
547
|
+
data: {
|
|
548
|
+
eventId: 'evt_test_123',
|
|
549
|
+
entityType: 'ORDER',
|
|
550
|
+
entityId: 'order_456',
|
|
551
|
+
},
|
|
552
|
+
headers: {
|
|
553
|
+
'fluent-signature': 'YOUR_TEST_SIGNATURE',
|
|
554
|
+
'content-type': 'application/json',
|
|
555
|
+
},
|
|
556
|
+
vars: {
|
|
557
|
+
FLUENT_WEBHOOK_PUBLIC_KEY: process.env.FLUENT_WEBHOOK_PUBLIC_KEY!,
|
|
558
|
+
},
|
|
559
|
+
request: {
|
|
560
|
+
text: async () =>
|
|
561
|
+
JSON.stringify({
|
|
562
|
+
eventId: 'evt_test_123',
|
|
563
|
+
entityType: 'ORDER',
|
|
564
|
+
entityId: 'order_456',
|
|
565
|
+
}),
|
|
566
|
+
},
|
|
567
|
+
log: console.log,
|
|
568
|
+
activation: {
|
|
569
|
+
id: 'test-activation',
|
|
570
|
+
workflowId: 'test-workflow',
|
|
571
|
+
},
|
|
572
|
+
};
|
|
573
|
+
|
|
574
|
+
// Test validation
|
|
575
|
+
const logger = toStructuredLogger(mockContext.log, {
|
|
576
|
+
service: 'test',
|
|
577
|
+
});
|
|
578
|
+
const validator = new WebhookValidationService({ strictValidation: true }, logger);
|
|
579
|
+
|
|
580
|
+
const rawBody = await mockContext.request.text();
|
|
581
|
+
const result = await validator.validateWebhook(
|
|
582
|
+
rawBody,
|
|
583
|
+
mockContext.headers,
|
|
584
|
+
mockContext.vars.FLUENT_WEBHOOK_PUBLIC_KEY
|
|
585
|
+
);
|
|
586
|
+
|
|
587
|
+
console.log('Test result:', result);
|
|
588
|
+
}
|
|
589
|
+
|
|
590
|
+
testVersoriWebhook();
|
|
591
|
+
```
|
|
592
|
+
|
|
593
|
+
### Versori Deploy & Test
|
|
594
|
+
|
|
595
|
+
```bash
|
|
596
|
+
# Deploy to Versori
|
|
597
|
+
npm run deploy
|
|
598
|
+
|
|
599
|
+
# Get webhook URL from Versori dashboard
|
|
600
|
+
# Example: https://yourspace.versori.run/webhooks/fluent-webhook
|
|
601
|
+
|
|
602
|
+
# Test with curl (get real signature from Fluent test environment)
|
|
603
|
+
curl -X POST https://yourspace.versori.run/webhooks/fluent-webhook \
|
|
604
|
+
-H "Content-Type: application/json" \
|
|
605
|
+
-H "fluent-signature: REAL_SIGNATURE_FROM_FLUENT" \
|
|
606
|
+
-d '{"eventId":"evt_123","entityType":"ORDER","entityId":"order_456"}'
|
|
607
|
+
|
|
608
|
+
# Check Versori logs
|
|
609
|
+
npm run logs
|
|
610
|
+
```
|
|
611
|
+
|
|
612
|
+
---
|
|
613
|
+
|
|
614
|
+
## Versori-Specific Edge Cases
|
|
615
|
+
|
|
616
|
+
### Edge Case 1: Missing Request Object (Old Versori)
|
|
617
|
+
|
|
618
|
+
```typescript
|
|
619
|
+
// Handle gracefully when ctx.request is undefined
|
|
620
|
+
const rawBody =
|
|
621
|
+
ctx.request && typeof ctx.request.text === 'function'
|
|
622
|
+
? await ctx.request.text()
|
|
623
|
+
: JSON.stringify(ctx.data); // Fallback for old versions
|
|
624
|
+
```
|
|
625
|
+
|
|
626
|
+
### Edge Case 2: Headers Case Sensitivity
|
|
627
|
+
|
|
628
|
+
```typescript
|
|
629
|
+
// Versori may normalize header names to lowercase
|
|
630
|
+
const req = ctx.request();
|
|
631
|
+
const headers = req?.headers || {};
|
|
632
|
+
const signature =
|
|
633
|
+
headers['fluent-signature'] || headers['Fluent-Signature'] || headers['FLUENT-SIGNATURE'];
|
|
634
|
+
|
|
635
|
+
// SDK handles this internally, but be aware for custom logic
|
|
636
|
+
```
|
|
637
|
+
|
|
638
|
+
### Edge Case 3: Empty Body
|
|
639
|
+
|
|
640
|
+
```typescript
|
|
641
|
+
// Handle webhooks with empty or null body
|
|
642
|
+
const rawBody =
|
|
643
|
+
ctx.request && typeof ctx.request.text === 'function'
|
|
644
|
+
? await ctx.request.text()
|
|
645
|
+
: ctx.data
|
|
646
|
+
? JSON.stringify(ctx.data)
|
|
647
|
+
: '{}';
|
|
648
|
+
|
|
649
|
+
if (!rawBody || rawBody === '{}') {
|
|
650
|
+
logger.warn('Received webhook with empty body');
|
|
651
|
+
return { statusCode: 400, body: 'Empty webhook body' };
|
|
652
|
+
}
|
|
653
|
+
```
|
|
654
|
+
|
|
655
|
+
---
|
|
656
|
+
|
|
657
|
+
## Key Takeaways
|
|
658
|
+
|
|
659
|
+
- **Use ctx.request.text()** for raw body in Versori v0.2.29+ (fallback to JSON.stringify for older versions)
|
|
660
|
+
- **Store public keys** in Versori variables as secrets
|
|
661
|
+
- **Version-aware code** handles both old and new Versori platforms
|
|
662
|
+
- **Validate before processing** - always check signature before business logic
|
|
663
|
+
- **Use workflow functions** - chain validation → processing for clean separation
|
|
664
|
+
- **Handle errors gracefully** - return proper HTTP status codes for validation failures
|
|
665
|
+
|
|
666
|
+
---
|
|
667
|
+
|
|
668
|
+
## Practice Exercise
|
|
669
|
+
|
|
670
|
+
Create a Versori webhook connector that:
|
|
671
|
+
|
|
672
|
+
1. Validates incoming Fluent Commerce webhooks
|
|
673
|
+
2. Handles both SHA512 and MD5 signatures
|
|
674
|
+
3. Logs validation results
|
|
675
|
+
4. Processes valid webhooks
|
|
676
|
+
5. Returns appropriate HTTP status codes
|
|
677
|
+
|
|
678
|
+
<details>
|
|
679
|
+
<summary>Solution</summary>
|
|
680
|
+
|
|
681
|
+
```typescript
|
|
682
|
+
import { webhook, fn } from '@versori/run';
|
|
683
|
+
import {
|
|
684
|
+
WebhookValidationService,
|
|
685
|
+
createConsoleLogger,
|
|
686
|
+
toStructuredLogger,
|
|
687
|
+
} from '@fluentcommerce/fc-connect-sdk';
|
|
688
|
+
|
|
689
|
+
export const practiceWebhook = webhook('practice-webhook')
|
|
690
|
+
.then(
|
|
691
|
+
fn('validate', async ctx => {
|
|
692
|
+
const { log } = ctx; // Versori native log
|
|
693
|
+
const logger = toStructuredLogger(log, {
|
|
694
|
+
service: 'practice',
|
|
695
|
+
});
|
|
696
|
+
const validator = new WebhookValidationService({ strictValidation: true }, logger);
|
|
697
|
+
|
|
698
|
+
const publicKey = ctx.vars.FLUENT_WEBHOOK_PUBLIC_KEY;
|
|
699
|
+
if (!publicKey) throw new Error('Missing public key');
|
|
700
|
+
|
|
701
|
+
const rawBody = await ctx.request.text(); // Versori v0.2.29+
|
|
702
|
+
const req = ctx.request();
|
|
703
|
+
const headers = req?.headers || {};
|
|
704
|
+
const result = await validator.validateWebhook(rawBody, headers, publicKey);
|
|
705
|
+
|
|
706
|
+
if (!result.isValid) {
|
|
707
|
+
throw new Error(`Validation failed: ${result.error}`);
|
|
708
|
+
}
|
|
709
|
+
|
|
710
|
+
logger.info('Validated with algorithm', { algorithm: result.algorithm });
|
|
711
|
+
return { ...ctx, data: JSON.parse(rawBody) };
|
|
712
|
+
})
|
|
713
|
+
)
|
|
714
|
+
.then(
|
|
715
|
+
fn('process', async ctx => {
|
|
716
|
+
ctx.log('Processing webhook', ctx.data.eventId);
|
|
717
|
+
// Your processing logic
|
|
718
|
+
return { success: true, eventId: ctx.data.eventId };
|
|
719
|
+
})
|
|
720
|
+
)
|
|
721
|
+
.catch(({ error }) => ({
|
|
722
|
+
statusCode: error.message.includes('Validation failed') ? 401 : 500,
|
|
723
|
+
body: { error: error.message },
|
|
724
|
+
}));
|
|
725
|
+
```
|
|
726
|
+
|
|
727
|
+
</details>
|
|
728
|
+
|
|
729
|
+
---
|
|
730
|
+
|
|
731
|
+
## Next Steps
|
|
732
|
+
|
|
733
|
+
Continue to [Module 4: Platform Integration](./webhook-validation-04-platform-integration.md) to learn how to integrate webhook validation with Express.js, Next.js, AWS Lambda, and other platforms.
|
|
734
|
+
|
|
735
|
+
---
|
|
736
|
+
|
|
737
|
+
## Further Reading
|
|
738
|
+
|
|
739
|
+
- [Versori Webhook Response Patterns](../../../04-REFERENCE/platforms/versori/modules/platforms-versori-04-workflows.md#critical-non-json-response-handlers-xml-html-csv) - Complete guide to Versori response handling
|
|
740
|
+
- [Module 5: Configuration](./webhook-validation-05-configuration.md) - Advanced configuration options
|
|
741
|
+
- [Module 6: Error Handling](./webhook-validation-06-error-handling.md) - Comprehensive error handling
|