signalwire-sdk 3.0.2.dev33__tar.gz → 3.0.2.dev35__tar.gz

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.
Files changed (209) hide show
  1. {signalwire_sdk-3.0.2.dev33/signalwire/signalwire_sdk.egg-info → signalwire_sdk-3.0.2.dev35}/PKG-INFO +1 -1
  2. {signalwire_sdk-3.0.2.dev33 → signalwire_sdk-3.0.2.dev35}/pyproject.toml +1 -1
  3. {signalwire_sdk-3.0.2.dev33 → signalwire_sdk-3.0.2.dev35}/signalwire/signalwire/__init__.py +1 -1
  4. {signalwire_sdk-3.0.2.dev33 → signalwire_sdk-3.0.2.dev35}/signalwire/signalwire/agent_server.py +1 -1
  5. {signalwire_sdk-3.0.2.dev33 → signalwire_sdk-3.0.2.dev35}/signalwire/signalwire/core/agent_base.py +33 -3
  6. {signalwire_sdk-3.0.2.dev33 → signalwire_sdk-3.0.2.dev35}/signalwire/signalwire/core/mixins/web_mixin.py +46 -20
  7. signalwire_sdk-3.0.2.dev35/signalwire/signalwire/core/security/__init__.py +26 -0
  8. signalwire_sdk-3.0.2.dev35/signalwire/signalwire/core/security/webhook_middleware.py +164 -0
  9. signalwire_sdk-3.0.2.dev35/signalwire/signalwire/core/security/webhook_validator.py +327 -0
  10. {signalwire_sdk-3.0.2.dev33 → signalwire_sdk-3.0.2.dev35/signalwire/signalwire_sdk.egg-info}/PKG-INFO +1 -1
  11. {signalwire_sdk-3.0.2.dev33 → signalwire_sdk-3.0.2.dev35}/signalwire/signalwire_sdk.egg-info/SOURCES.txt +2 -0
  12. signalwire_sdk-3.0.2.dev33/signalwire/signalwire/core/security/__init__.py +0 -9
  13. {signalwire_sdk-3.0.2.dev33 → signalwire_sdk-3.0.2.dev35}/LICENSE +0 -0
  14. {signalwire_sdk-3.0.2.dev33 → signalwire_sdk-3.0.2.dev35}/README.md +0 -0
  15. {signalwire_sdk-3.0.2.dev33 → signalwire_sdk-3.0.2.dev35}/man/sw-agent-init.1 +0 -0
  16. {signalwire_sdk-3.0.2.dev33 → signalwire_sdk-3.0.2.dev35}/man/sw-search.1 +0 -0
  17. {signalwire_sdk-3.0.2.dev33 → signalwire_sdk-3.0.2.dev35}/man/swaig-test.1 +0 -0
  18. {signalwire_sdk-3.0.2.dev33 → signalwire_sdk-3.0.2.dev35}/setup.cfg +0 -0
  19. {signalwire_sdk-3.0.2.dev33 → signalwire_sdk-3.0.2.dev35}/setup.py +0 -0
  20. {signalwire_sdk-3.0.2.dev33 → signalwire_sdk-3.0.2.dev35}/signalwire/signalwire/agents/bedrock.py +0 -0
  21. {signalwire_sdk-3.0.2.dev33 → signalwire_sdk-3.0.2.dev35}/signalwire/signalwire/cli/__init__.py +0 -0
  22. {signalwire_sdk-3.0.2.dev33 → signalwire_sdk-3.0.2.dev35}/signalwire/signalwire/cli/build_search.py +0 -0
  23. {signalwire_sdk-3.0.2.dev33 → signalwire_sdk-3.0.2.dev35}/signalwire/signalwire/cli/config.py +0 -0
  24. {signalwire_sdk-3.0.2.dev33 → signalwire_sdk-3.0.2.dev35}/signalwire/signalwire/cli/core/__init__.py +0 -0
  25. {signalwire_sdk-3.0.2.dev33 → signalwire_sdk-3.0.2.dev35}/signalwire/signalwire/cli/core/agent_loader.py +0 -0
  26. {signalwire_sdk-3.0.2.dev33 → signalwire_sdk-3.0.2.dev35}/signalwire/signalwire/cli/core/argparse_helpers.py +0 -0
  27. {signalwire_sdk-3.0.2.dev33 → signalwire_sdk-3.0.2.dev35}/signalwire/signalwire/cli/core/dynamic_config.py +0 -0
  28. {signalwire_sdk-3.0.2.dev33 → signalwire_sdk-3.0.2.dev35}/signalwire/signalwire/cli/core/service_loader.py +0 -0
  29. {signalwire_sdk-3.0.2.dev33 → signalwire_sdk-3.0.2.dev35}/signalwire/signalwire/cli/dokku.py +0 -0
  30. {signalwire_sdk-3.0.2.dev33 → signalwire_sdk-3.0.2.dev35}/signalwire/signalwire/cli/execution/__init__.py +0 -0
  31. {signalwire_sdk-3.0.2.dev33 → signalwire_sdk-3.0.2.dev35}/signalwire/signalwire/cli/execution/datamap_exec.py +0 -0
  32. {signalwire_sdk-3.0.2.dev33 → signalwire_sdk-3.0.2.dev35}/signalwire/signalwire/cli/execution/webhook_exec.py +0 -0
  33. {signalwire_sdk-3.0.2.dev33 → signalwire_sdk-3.0.2.dev35}/signalwire/signalwire/cli/init_project.py +0 -0
  34. {signalwire_sdk-3.0.2.dev33 → signalwire_sdk-3.0.2.dev35}/signalwire/signalwire/cli/output/__init__.py +0 -0
  35. {signalwire_sdk-3.0.2.dev33 → signalwire_sdk-3.0.2.dev35}/signalwire/signalwire/cli/output/output_formatter.py +0 -0
  36. {signalwire_sdk-3.0.2.dev33 → signalwire_sdk-3.0.2.dev35}/signalwire/signalwire/cli/output/swml_dump.py +0 -0
  37. {signalwire_sdk-3.0.2.dev33 → signalwire_sdk-3.0.2.dev35}/signalwire/signalwire/cli/simulation/__init__.py +0 -0
  38. {signalwire_sdk-3.0.2.dev33 → signalwire_sdk-3.0.2.dev35}/signalwire/signalwire/cli/simulation/data_generation.py +0 -0
  39. {signalwire_sdk-3.0.2.dev33 → signalwire_sdk-3.0.2.dev35}/signalwire/signalwire/cli/simulation/data_overrides.py +0 -0
  40. {signalwire_sdk-3.0.2.dev33 → signalwire_sdk-3.0.2.dev35}/signalwire/signalwire/cli/simulation/mock_env.py +0 -0
  41. {signalwire_sdk-3.0.2.dev33 → signalwire_sdk-3.0.2.dev35}/signalwire/signalwire/cli/swaig_test_wrapper.py +0 -0
  42. {signalwire_sdk-3.0.2.dev33 → signalwire_sdk-3.0.2.dev35}/signalwire/signalwire/cli/test_swaig.py +0 -0
  43. {signalwire_sdk-3.0.2.dev33 → signalwire_sdk-3.0.2.dev35}/signalwire/signalwire/cli/types.py +0 -0
  44. {signalwire_sdk-3.0.2.dev33 → signalwire_sdk-3.0.2.dev35}/signalwire/signalwire/core/__init__.py +0 -0
  45. {signalwire_sdk-3.0.2.dev33 → signalwire_sdk-3.0.2.dev35}/signalwire/signalwire/core/agent/__init__.py +0 -0
  46. {signalwire_sdk-3.0.2.dev33 → signalwire_sdk-3.0.2.dev35}/signalwire/signalwire/core/agent/config/__init__.py +0 -0
  47. {signalwire_sdk-3.0.2.dev33 → signalwire_sdk-3.0.2.dev35}/signalwire/signalwire/core/agent/deployment/__init__.py +0 -0
  48. {signalwire_sdk-3.0.2.dev33 → signalwire_sdk-3.0.2.dev35}/signalwire/signalwire/core/agent/deployment/handlers/__init__.py +0 -0
  49. {signalwire_sdk-3.0.2.dev33 → signalwire_sdk-3.0.2.dev35}/signalwire/signalwire/core/agent/prompt/__init__.py +0 -0
  50. {signalwire_sdk-3.0.2.dev33 → signalwire_sdk-3.0.2.dev35}/signalwire/signalwire/core/agent/prompt/manager.py +0 -0
  51. {signalwire_sdk-3.0.2.dev33 → signalwire_sdk-3.0.2.dev35}/signalwire/signalwire/core/agent/routing/__init__.py +0 -0
  52. {signalwire_sdk-3.0.2.dev33 → signalwire_sdk-3.0.2.dev35}/signalwire/signalwire/core/agent/security/__init__.py +0 -0
  53. {signalwire_sdk-3.0.2.dev33 → signalwire_sdk-3.0.2.dev35}/signalwire/signalwire/core/agent/swml/__init__.py +0 -0
  54. {signalwire_sdk-3.0.2.dev33 → signalwire_sdk-3.0.2.dev35}/signalwire/signalwire/core/agent/tools/__init__.py +0 -0
  55. {signalwire_sdk-3.0.2.dev33 → signalwire_sdk-3.0.2.dev35}/signalwire/signalwire/core/agent/tools/decorator.py +0 -0
  56. {signalwire_sdk-3.0.2.dev33 → signalwire_sdk-3.0.2.dev35}/signalwire/signalwire/core/agent/tools/registry.py +0 -0
  57. {signalwire_sdk-3.0.2.dev33 → signalwire_sdk-3.0.2.dev35}/signalwire/signalwire/core/agent/tools/type_inference.py +0 -0
  58. {signalwire_sdk-3.0.2.dev33 → signalwire_sdk-3.0.2.dev35}/signalwire/signalwire/core/auth_handler.py +0 -0
  59. {signalwire_sdk-3.0.2.dev33 → signalwire_sdk-3.0.2.dev35}/signalwire/signalwire/core/config_loader.py +0 -0
  60. {signalwire_sdk-3.0.2.dev33 → signalwire_sdk-3.0.2.dev35}/signalwire/signalwire/core/contexts.py +0 -0
  61. {signalwire_sdk-3.0.2.dev33 → signalwire_sdk-3.0.2.dev35}/signalwire/signalwire/core/data_map.py +0 -0
  62. {signalwire_sdk-3.0.2.dev33 → signalwire_sdk-3.0.2.dev35}/signalwire/signalwire/core/function_result.py +0 -0
  63. {signalwire_sdk-3.0.2.dev33 → signalwire_sdk-3.0.2.dev35}/signalwire/signalwire/core/logging_config.py +0 -0
  64. {signalwire_sdk-3.0.2.dev33 → signalwire_sdk-3.0.2.dev35}/signalwire/signalwire/core/mixins/__init__.py +0 -0
  65. {signalwire_sdk-3.0.2.dev33 → signalwire_sdk-3.0.2.dev35}/signalwire/signalwire/core/mixins/ai_config_mixin.py +0 -0
  66. {signalwire_sdk-3.0.2.dev33 → signalwire_sdk-3.0.2.dev35}/signalwire/signalwire/core/mixins/auth_mixin.py +0 -0
  67. {signalwire_sdk-3.0.2.dev33 → signalwire_sdk-3.0.2.dev35}/signalwire/signalwire/core/mixins/mcp_server_mixin.py +0 -0
  68. {signalwire_sdk-3.0.2.dev33 → signalwire_sdk-3.0.2.dev35}/signalwire/signalwire/core/mixins/prompt_mixin.py +0 -0
  69. {signalwire_sdk-3.0.2.dev33 → signalwire_sdk-3.0.2.dev35}/signalwire/signalwire/core/mixins/serverless_mixin.py +0 -0
  70. {signalwire_sdk-3.0.2.dev33 → signalwire_sdk-3.0.2.dev35}/signalwire/signalwire/core/mixins/skill_mixin.py +0 -0
  71. {signalwire_sdk-3.0.2.dev33 → signalwire_sdk-3.0.2.dev35}/signalwire/signalwire/core/mixins/state_mixin.py +0 -0
  72. {signalwire_sdk-3.0.2.dev33 → signalwire_sdk-3.0.2.dev35}/signalwire/signalwire/core/mixins/tool_mixin.py +0 -0
  73. {signalwire_sdk-3.0.2.dev33 → signalwire_sdk-3.0.2.dev35}/signalwire/signalwire/core/pom_builder.py +0 -0
  74. {signalwire_sdk-3.0.2.dev33 → signalwire_sdk-3.0.2.dev35}/signalwire/signalwire/core/security/session_manager.py +0 -0
  75. {signalwire_sdk-3.0.2.dev33 → signalwire_sdk-3.0.2.dev35}/signalwire/signalwire/core/security_config.py +0 -0
  76. {signalwire_sdk-3.0.2.dev33 → signalwire_sdk-3.0.2.dev35}/signalwire/signalwire/core/skill_base.py +0 -0
  77. {signalwire_sdk-3.0.2.dev33 → signalwire_sdk-3.0.2.dev35}/signalwire/signalwire/core/skill_manager.py +0 -0
  78. {signalwire_sdk-3.0.2.dev33 → signalwire_sdk-3.0.2.dev35}/signalwire/signalwire/core/swaig_function.py +0 -0
  79. {signalwire_sdk-3.0.2.dev33 → signalwire_sdk-3.0.2.dev35}/signalwire/signalwire/core/swml_builder.py +0 -0
  80. {signalwire_sdk-3.0.2.dev33 → signalwire_sdk-3.0.2.dev35}/signalwire/signalwire/core/swml_handler.py +0 -0
  81. {signalwire_sdk-3.0.2.dev33 → signalwire_sdk-3.0.2.dev35}/signalwire/signalwire/core/swml_renderer.py +0 -0
  82. {signalwire_sdk-3.0.2.dev33 → signalwire_sdk-3.0.2.dev35}/signalwire/signalwire/core/swml_service.py +0 -0
  83. {signalwire_sdk-3.0.2.dev33 → signalwire_sdk-3.0.2.dev35}/signalwire/signalwire/livewire/__init__.py +0 -0
  84. {signalwire_sdk-3.0.2.dev33 → signalwire_sdk-3.0.2.dev35}/signalwire/signalwire/livewire/plugins.py +0 -0
  85. {signalwire_sdk-3.0.2.dev33 → signalwire_sdk-3.0.2.dev35}/signalwire/signalwire/mcp_gateway/__init__.py +0 -0
  86. {signalwire_sdk-3.0.2.dev33 → signalwire_sdk-3.0.2.dev35}/signalwire/signalwire/mcp_gateway/gateway_service.py +0 -0
  87. {signalwire_sdk-3.0.2.dev33 → signalwire_sdk-3.0.2.dev35}/signalwire/signalwire/mcp_gateway/mcp_manager.py +0 -0
  88. {signalwire_sdk-3.0.2.dev33 → signalwire_sdk-3.0.2.dev35}/signalwire/signalwire/mcp_gateway/session_manager.py +0 -0
  89. {signalwire_sdk-3.0.2.dev33 → signalwire_sdk-3.0.2.dev35}/signalwire/signalwire/pom/__init__.py +0 -0
  90. {signalwire_sdk-3.0.2.dev33 → signalwire_sdk-3.0.2.dev35}/signalwire/signalwire/pom/pom.py +0 -0
  91. {signalwire_sdk-3.0.2.dev33 → signalwire_sdk-3.0.2.dev35}/signalwire/signalwire/pom/pom_tool.py +0 -0
  92. {signalwire_sdk-3.0.2.dev33 → signalwire_sdk-3.0.2.dev35}/signalwire/signalwire/prefabs/__init__.py +0 -0
  93. {signalwire_sdk-3.0.2.dev33 → signalwire_sdk-3.0.2.dev35}/signalwire/signalwire/prefabs/concierge.py +0 -0
  94. {signalwire_sdk-3.0.2.dev33 → signalwire_sdk-3.0.2.dev35}/signalwire/signalwire/prefabs/faq_bot.py +0 -0
  95. {signalwire_sdk-3.0.2.dev33 → signalwire_sdk-3.0.2.dev35}/signalwire/signalwire/prefabs/info_gatherer.py +0 -0
  96. {signalwire_sdk-3.0.2.dev33 → signalwire_sdk-3.0.2.dev35}/signalwire/signalwire/prefabs/receptionist.py +0 -0
  97. {signalwire_sdk-3.0.2.dev33 → signalwire_sdk-3.0.2.dev35}/signalwire/signalwire/prefabs/survey.py +0 -0
  98. {signalwire_sdk-3.0.2.dev33 → signalwire_sdk-3.0.2.dev35}/signalwire/signalwire/relay/__init__.py +0 -0
  99. {signalwire_sdk-3.0.2.dev33 → signalwire_sdk-3.0.2.dev35}/signalwire/signalwire/relay/call.py +0 -0
  100. {signalwire_sdk-3.0.2.dev33 → signalwire_sdk-3.0.2.dev35}/signalwire/signalwire/relay/client.py +0 -0
  101. {signalwire_sdk-3.0.2.dev33 → signalwire_sdk-3.0.2.dev35}/signalwire/signalwire/relay/constants.py +0 -0
  102. {signalwire_sdk-3.0.2.dev33 → signalwire_sdk-3.0.2.dev35}/signalwire/signalwire/relay/event.py +0 -0
  103. {signalwire_sdk-3.0.2.dev33 → signalwire_sdk-3.0.2.dev35}/signalwire/signalwire/relay/message.py +0 -0
  104. {signalwire_sdk-3.0.2.dev33 → signalwire_sdk-3.0.2.dev35}/signalwire/signalwire/rest/__init__.py +0 -0
  105. {signalwire_sdk-3.0.2.dev33 → signalwire_sdk-3.0.2.dev35}/signalwire/signalwire/rest/_base.py +0 -0
  106. {signalwire_sdk-3.0.2.dev33 → signalwire_sdk-3.0.2.dev35}/signalwire/signalwire/rest/_pagination.py +0 -0
  107. {signalwire_sdk-3.0.2.dev33 → signalwire_sdk-3.0.2.dev35}/signalwire/signalwire/rest/call_handler.py +0 -0
  108. {signalwire_sdk-3.0.2.dev33 → signalwire_sdk-3.0.2.dev35}/signalwire/signalwire/rest/client.py +0 -0
  109. {signalwire_sdk-3.0.2.dev33 → signalwire_sdk-3.0.2.dev35}/signalwire/signalwire/rest/namespaces/__init__.py +0 -0
  110. {signalwire_sdk-3.0.2.dev33 → signalwire_sdk-3.0.2.dev35}/signalwire/signalwire/rest/namespaces/addresses.py +0 -0
  111. {signalwire_sdk-3.0.2.dev33 → signalwire_sdk-3.0.2.dev35}/signalwire/signalwire/rest/namespaces/calling.py +0 -0
  112. {signalwire_sdk-3.0.2.dev33 → signalwire_sdk-3.0.2.dev35}/signalwire/signalwire/rest/namespaces/chat.py +0 -0
  113. {signalwire_sdk-3.0.2.dev33 → signalwire_sdk-3.0.2.dev35}/signalwire/signalwire/rest/namespaces/compat.py +0 -0
  114. {signalwire_sdk-3.0.2.dev33 → signalwire_sdk-3.0.2.dev35}/signalwire/signalwire/rest/namespaces/datasphere.py +0 -0
  115. {signalwire_sdk-3.0.2.dev33 → signalwire_sdk-3.0.2.dev35}/signalwire/signalwire/rest/namespaces/fabric.py +0 -0
  116. {signalwire_sdk-3.0.2.dev33 → signalwire_sdk-3.0.2.dev35}/signalwire/signalwire/rest/namespaces/imported_numbers.py +0 -0
  117. {signalwire_sdk-3.0.2.dev33 → signalwire_sdk-3.0.2.dev35}/signalwire/signalwire/rest/namespaces/logs.py +0 -0
  118. {signalwire_sdk-3.0.2.dev33 → signalwire_sdk-3.0.2.dev35}/signalwire/signalwire/rest/namespaces/lookup.py +0 -0
  119. {signalwire_sdk-3.0.2.dev33 → signalwire_sdk-3.0.2.dev35}/signalwire/signalwire/rest/namespaces/mfa.py +0 -0
  120. {signalwire_sdk-3.0.2.dev33 → signalwire_sdk-3.0.2.dev35}/signalwire/signalwire/rest/namespaces/number_groups.py +0 -0
  121. {signalwire_sdk-3.0.2.dev33 → signalwire_sdk-3.0.2.dev35}/signalwire/signalwire/rest/namespaces/phone_numbers.py +0 -0
  122. {signalwire_sdk-3.0.2.dev33 → signalwire_sdk-3.0.2.dev35}/signalwire/signalwire/rest/namespaces/project.py +0 -0
  123. {signalwire_sdk-3.0.2.dev33 → signalwire_sdk-3.0.2.dev35}/signalwire/signalwire/rest/namespaces/pubsub.py +0 -0
  124. {signalwire_sdk-3.0.2.dev33 → signalwire_sdk-3.0.2.dev35}/signalwire/signalwire/rest/namespaces/queues.py +0 -0
  125. {signalwire_sdk-3.0.2.dev33 → signalwire_sdk-3.0.2.dev35}/signalwire/signalwire/rest/namespaces/recordings.py +0 -0
  126. {signalwire_sdk-3.0.2.dev33 → signalwire_sdk-3.0.2.dev35}/signalwire/signalwire/rest/namespaces/registry.py +0 -0
  127. {signalwire_sdk-3.0.2.dev33 → signalwire_sdk-3.0.2.dev35}/signalwire/signalwire/rest/namespaces/short_codes.py +0 -0
  128. {signalwire_sdk-3.0.2.dev33 → signalwire_sdk-3.0.2.dev35}/signalwire/signalwire/rest/namespaces/sip_profile.py +0 -0
  129. {signalwire_sdk-3.0.2.dev33 → signalwire_sdk-3.0.2.dev35}/signalwire/signalwire/rest/namespaces/verified_callers.py +0 -0
  130. {signalwire_sdk-3.0.2.dev33 → signalwire_sdk-3.0.2.dev35}/signalwire/signalwire/rest/namespaces/video.py +0 -0
  131. {signalwire_sdk-3.0.2.dev33 → signalwire_sdk-3.0.2.dev35}/signalwire/signalwire/schema.json +0 -0
  132. {signalwire_sdk-3.0.2.dev33 → signalwire_sdk-3.0.2.dev35}/signalwire/signalwire/search/__init__.py +0 -0
  133. {signalwire_sdk-3.0.2.dev33 → signalwire_sdk-3.0.2.dev35}/signalwire/signalwire/search/document_processor.py +0 -0
  134. {signalwire_sdk-3.0.2.dev33 → signalwire_sdk-3.0.2.dev35}/signalwire/signalwire/search/index_builder.py +0 -0
  135. {signalwire_sdk-3.0.2.dev33 → signalwire_sdk-3.0.2.dev35}/signalwire/signalwire/search/migration.py +0 -0
  136. {signalwire_sdk-3.0.2.dev33 → signalwire_sdk-3.0.2.dev35}/signalwire/signalwire/search/models.py +0 -0
  137. {signalwire_sdk-3.0.2.dev33 → signalwire_sdk-3.0.2.dev35}/signalwire/signalwire/search/pgvector_backend.py +0 -0
  138. {signalwire_sdk-3.0.2.dev33 → signalwire_sdk-3.0.2.dev35}/signalwire/signalwire/search/query_processor.py +0 -0
  139. {signalwire_sdk-3.0.2.dev33 → signalwire_sdk-3.0.2.dev35}/signalwire/signalwire/search/search_engine.py +0 -0
  140. {signalwire_sdk-3.0.2.dev33 → signalwire_sdk-3.0.2.dev35}/signalwire/signalwire/search/search_service.py +0 -0
  141. {signalwire_sdk-3.0.2.dev33 → signalwire_sdk-3.0.2.dev35}/signalwire/signalwire/skills/README.md +0 -0
  142. {signalwire_sdk-3.0.2.dev33 → signalwire_sdk-3.0.2.dev35}/signalwire/signalwire/skills/__init__.py +0 -0
  143. {signalwire_sdk-3.0.2.dev33 → signalwire_sdk-3.0.2.dev35}/signalwire/signalwire/skills/api_ninjas_trivia/README.md +0 -0
  144. {signalwire_sdk-3.0.2.dev33 → signalwire_sdk-3.0.2.dev35}/signalwire/signalwire/skills/api_ninjas_trivia/__init__.py +0 -0
  145. {signalwire_sdk-3.0.2.dev33 → signalwire_sdk-3.0.2.dev35}/signalwire/signalwire/skills/api_ninjas_trivia/skill.py +0 -0
  146. {signalwire_sdk-3.0.2.dev33 → signalwire_sdk-3.0.2.dev35}/signalwire/signalwire/skills/claude_skills/README.md +0 -0
  147. {signalwire_sdk-3.0.2.dev33 → signalwire_sdk-3.0.2.dev35}/signalwire/signalwire/skills/claude_skills/__init__.py +0 -0
  148. {signalwire_sdk-3.0.2.dev33 → signalwire_sdk-3.0.2.dev35}/signalwire/signalwire/skills/claude_skills/skill.py +0 -0
  149. {signalwire_sdk-3.0.2.dev33 → signalwire_sdk-3.0.2.dev35}/signalwire/signalwire/skills/datasphere/README.md +0 -0
  150. {signalwire_sdk-3.0.2.dev33 → signalwire_sdk-3.0.2.dev35}/signalwire/signalwire/skills/datasphere/__init__.py +0 -0
  151. {signalwire_sdk-3.0.2.dev33 → signalwire_sdk-3.0.2.dev35}/signalwire/signalwire/skills/datasphere/skill.py +0 -0
  152. {signalwire_sdk-3.0.2.dev33 → signalwire_sdk-3.0.2.dev35}/signalwire/signalwire/skills/datasphere_serverless/README.md +0 -0
  153. {signalwire_sdk-3.0.2.dev33 → signalwire_sdk-3.0.2.dev35}/signalwire/signalwire/skills/datasphere_serverless/__init__.py +0 -0
  154. {signalwire_sdk-3.0.2.dev33 → signalwire_sdk-3.0.2.dev35}/signalwire/signalwire/skills/datasphere_serverless/skill.py +0 -0
  155. {signalwire_sdk-3.0.2.dev33 → signalwire_sdk-3.0.2.dev35}/signalwire/signalwire/skills/datetime/README.md +0 -0
  156. {signalwire_sdk-3.0.2.dev33 → signalwire_sdk-3.0.2.dev35}/signalwire/signalwire/skills/datetime/__init__.py +0 -0
  157. {signalwire_sdk-3.0.2.dev33 → signalwire_sdk-3.0.2.dev35}/signalwire/signalwire/skills/datetime/skill.py +0 -0
  158. {signalwire_sdk-3.0.2.dev33 → signalwire_sdk-3.0.2.dev35}/signalwire/signalwire/skills/google_maps/__init__.py +0 -0
  159. {signalwire_sdk-3.0.2.dev33 → signalwire_sdk-3.0.2.dev35}/signalwire/signalwire/skills/google_maps/skill.py +0 -0
  160. {signalwire_sdk-3.0.2.dev33 → signalwire_sdk-3.0.2.dev35}/signalwire/signalwire/skills/info_gatherer/README.md +0 -0
  161. {signalwire_sdk-3.0.2.dev33 → signalwire_sdk-3.0.2.dev35}/signalwire/signalwire/skills/info_gatherer/__init__.py +0 -0
  162. {signalwire_sdk-3.0.2.dev33 → signalwire_sdk-3.0.2.dev35}/signalwire/signalwire/skills/info_gatherer/skill.py +0 -0
  163. {signalwire_sdk-3.0.2.dev33 → signalwire_sdk-3.0.2.dev35}/signalwire/signalwire/skills/joke/README.md +0 -0
  164. {signalwire_sdk-3.0.2.dev33 → signalwire_sdk-3.0.2.dev35}/signalwire/signalwire/skills/joke/__init__.py +0 -0
  165. {signalwire_sdk-3.0.2.dev33 → signalwire_sdk-3.0.2.dev35}/signalwire/signalwire/skills/joke/skill.py +0 -0
  166. {signalwire_sdk-3.0.2.dev33 → signalwire_sdk-3.0.2.dev35}/signalwire/signalwire/skills/math/README.md +0 -0
  167. {signalwire_sdk-3.0.2.dev33 → signalwire_sdk-3.0.2.dev35}/signalwire/signalwire/skills/math/__init__.py +0 -0
  168. {signalwire_sdk-3.0.2.dev33 → signalwire_sdk-3.0.2.dev35}/signalwire/signalwire/skills/math/skill.py +0 -0
  169. {signalwire_sdk-3.0.2.dev33 → signalwire_sdk-3.0.2.dev35}/signalwire/signalwire/skills/mcp_gateway/README.md +0 -0
  170. {signalwire_sdk-3.0.2.dev33 → signalwire_sdk-3.0.2.dev35}/signalwire/signalwire/skills/mcp_gateway/__init__.py +0 -0
  171. {signalwire_sdk-3.0.2.dev33 → signalwire_sdk-3.0.2.dev35}/signalwire/signalwire/skills/mcp_gateway/skill.py +0 -0
  172. {signalwire_sdk-3.0.2.dev33 → signalwire_sdk-3.0.2.dev35}/signalwire/signalwire/skills/native_vector_search/README.md +0 -0
  173. {signalwire_sdk-3.0.2.dev33 → signalwire_sdk-3.0.2.dev35}/signalwire/signalwire/skills/native_vector_search/__init__.py +0 -0
  174. {signalwire_sdk-3.0.2.dev33 → signalwire_sdk-3.0.2.dev35}/signalwire/signalwire/skills/native_vector_search/skill.py +0 -0
  175. {signalwire_sdk-3.0.2.dev33 → signalwire_sdk-3.0.2.dev35}/signalwire/signalwire/skills/play_background_file/README.md +0 -0
  176. {signalwire_sdk-3.0.2.dev33 → signalwire_sdk-3.0.2.dev35}/signalwire/signalwire/skills/play_background_file/__init__.py +0 -0
  177. {signalwire_sdk-3.0.2.dev33 → signalwire_sdk-3.0.2.dev35}/signalwire/signalwire/skills/play_background_file/skill.py +0 -0
  178. {signalwire_sdk-3.0.2.dev33 → signalwire_sdk-3.0.2.dev35}/signalwire/signalwire/skills/registry.py +0 -0
  179. {signalwire_sdk-3.0.2.dev33 → signalwire_sdk-3.0.2.dev35}/signalwire/signalwire/skills/spider/README.md +0 -0
  180. {signalwire_sdk-3.0.2.dev33 → signalwire_sdk-3.0.2.dev35}/signalwire/signalwire/skills/spider/__init__.py +0 -0
  181. {signalwire_sdk-3.0.2.dev33 → signalwire_sdk-3.0.2.dev35}/signalwire/signalwire/skills/spider/skill.py +0 -0
  182. {signalwire_sdk-3.0.2.dev33 → signalwire_sdk-3.0.2.dev35}/signalwire/signalwire/skills/swml_transfer/README.md +0 -0
  183. {signalwire_sdk-3.0.2.dev33 → signalwire_sdk-3.0.2.dev35}/signalwire/signalwire/skills/swml_transfer/__init__.py +0 -0
  184. {signalwire_sdk-3.0.2.dev33 → signalwire_sdk-3.0.2.dev35}/signalwire/signalwire/skills/swml_transfer/skill.py +0 -0
  185. {signalwire_sdk-3.0.2.dev33 → signalwire_sdk-3.0.2.dev35}/signalwire/signalwire/skills/weather_api/README.md +0 -0
  186. {signalwire_sdk-3.0.2.dev33 → signalwire_sdk-3.0.2.dev35}/signalwire/signalwire/skills/weather_api/__init__.py +0 -0
  187. {signalwire_sdk-3.0.2.dev33 → signalwire_sdk-3.0.2.dev35}/signalwire/signalwire/skills/weather_api/skill.py +0 -0
  188. {signalwire_sdk-3.0.2.dev33 → signalwire_sdk-3.0.2.dev35}/signalwire/signalwire/skills/web_search/README.md +0 -0
  189. {signalwire_sdk-3.0.2.dev33 → signalwire_sdk-3.0.2.dev35}/signalwire/signalwire/skills/web_search/__init__.py +0 -0
  190. {signalwire_sdk-3.0.2.dev33 → signalwire_sdk-3.0.2.dev35}/signalwire/signalwire/skills/web_search/skill.py +0 -0
  191. {signalwire_sdk-3.0.2.dev33 → signalwire_sdk-3.0.2.dev35}/signalwire/signalwire/skills/web_search/skill_improved.py +0 -0
  192. {signalwire_sdk-3.0.2.dev33 → signalwire_sdk-3.0.2.dev35}/signalwire/signalwire/skills/web_search/skill_original.py +0 -0
  193. {signalwire_sdk-3.0.2.dev33 → signalwire_sdk-3.0.2.dev35}/signalwire/signalwire/skills/wikipedia_search/README.md +0 -0
  194. {signalwire_sdk-3.0.2.dev33 → signalwire_sdk-3.0.2.dev35}/signalwire/signalwire/skills/wikipedia_search/__init__.py +0 -0
  195. {signalwire_sdk-3.0.2.dev33 → signalwire_sdk-3.0.2.dev35}/signalwire/signalwire/skills/wikipedia_search/skill.py +0 -0
  196. {signalwire_sdk-3.0.2.dev33 → signalwire_sdk-3.0.2.dev35}/signalwire/signalwire/utils/__init__.py +0 -0
  197. {signalwire_sdk-3.0.2.dev33 → signalwire_sdk-3.0.2.dev35}/signalwire/signalwire/utils/pom_utils.py +0 -0
  198. {signalwire_sdk-3.0.2.dev33 → signalwire_sdk-3.0.2.dev35}/signalwire/signalwire/utils/schema_utils.py +0 -0
  199. {signalwire_sdk-3.0.2.dev33 → signalwire_sdk-3.0.2.dev35}/signalwire/signalwire/utils/token_generators.py +0 -0
  200. {signalwire_sdk-3.0.2.dev33 → signalwire_sdk-3.0.2.dev35}/signalwire/signalwire/utils/url_validator.py +0 -0
  201. {signalwire_sdk-3.0.2.dev33 → signalwire_sdk-3.0.2.dev35}/signalwire/signalwire/utils/validators.py +0 -0
  202. {signalwire_sdk-3.0.2.dev33 → signalwire_sdk-3.0.2.dev35}/signalwire/signalwire/web/__init__.py +0 -0
  203. {signalwire_sdk-3.0.2.dev33 → signalwire_sdk-3.0.2.dev35}/signalwire/signalwire/web/web_service.py +0 -0
  204. {signalwire_sdk-3.0.2.dev33 → signalwire_sdk-3.0.2.dev35}/signalwire/signalwire_sdk.egg-info/dependency_links.txt +0 -0
  205. {signalwire_sdk-3.0.2.dev33 → signalwire_sdk-3.0.2.dev35}/signalwire/signalwire_sdk.egg-info/entry_points.txt +0 -0
  206. {signalwire_sdk-3.0.2.dev33 → signalwire_sdk-3.0.2.dev35}/signalwire/signalwire_sdk.egg-info/requires.txt +0 -0
  207. {signalwire_sdk-3.0.2.dev33 → signalwire_sdk-3.0.2.dev35}/signalwire/signalwire_sdk.egg-info/top_level.txt +0 -0
  208. {signalwire_sdk-3.0.2.dev33 → signalwire_sdk-3.0.2.dev35}/tests/test_examples.py +0 -0
  209. {signalwire_sdk-3.0.2.dev33 → signalwire_sdk-3.0.2.dev35}/tests/test_mcp_integration.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: signalwire-sdk
3
- Version: 3.0.2.dev33
3
+ Version: 3.0.2.dev35
4
4
  Summary: SignalWire SDK
5
5
  Author-email: SignalWire Team <info@signalwire.com>
6
6
  License: MIT
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "signalwire-sdk"
7
- version = "3.0.2.dev33"
7
+ version = "3.0.2.dev35"
8
8
  description = "SignalWire SDK"
9
9
  authors = [
10
10
  {name = "SignalWire Team", email = "info@signalwire.com"}
@@ -18,7 +18,7 @@ A package for building AI agents using SignalWire's AI and SWML capabilities.
18
18
  from .core.logging_config import configure_logging
19
19
  configure_logging()
20
20
 
21
- __version__ = "3.0.2.dev33"
21
+ __version__ = "3.0.2.dev35"
22
22
 
23
23
  # Import core classes for easier access
24
24
  from .core.agent_base import AgentBase
@@ -61,7 +61,7 @@ class AgentServer:
61
61
  self.app = FastAPI(
62
62
  title="SignalWire AI Agents",
63
63
  description="Hosted SignalWire AI Agents",
64
- version="3.0.2.dev33",
64
+ version="3.0.2.dev35",
65
65
  redirect_slashes=False,
66
66
  docs_url=None,
67
67
  redoc_url=None,
@@ -126,11 +126,13 @@ class AgentBase(
126
126
  enable_post_prompt_override: bool = False,
127
127
  check_for_input_override: bool = False,
128
128
  config_file: Optional[str] = None,
129
- schema_validation: bool = True
129
+ schema_validation: bool = True,
130
+ signing_key: Optional[str] = None,
131
+ trust_proxy_for_signature: bool = False
130
132
  ):
131
133
  """
132
134
  Initialize a new agent
133
-
135
+
134
136
  Args:
135
137
  name: Agent name/identifier
136
138
  route: HTTP route path for this agent
@@ -153,6 +155,17 @@ class AgentBase(
153
155
  config_file: Optional path to configuration file
154
156
  schema_validation: Enable SWML schema validation. Default True. Can also
155
157
  be disabled via SWML_SKIP_SCHEMA_VALIDATION=1 env var.
158
+ signing_key: Optional SignalWire Signing Key (from Dashboard → API
159
+ Credentials). When set, webhook signature validation is
160
+ enforced on POST /, /swaig, /post_prompt — unsigned or
161
+ invalidly-signed requests get a 403. Falls back to the
162
+ SIGNALWIRE_SIGNING_KEY env var if not passed.
163
+ See porting-sdk/webhooks.md for the contract.
164
+ trust_proxy_for_signature: If True, honor X-Forwarded-Proto /
165
+ X-Forwarded-Host when reconstructing the URL during
166
+ signature validation. Default False — proxy headers
167
+ are spoofable, so opt in only when you control the
168
+ proxy chain.
156
169
  """
157
170
  # Import SWMLService here to avoid circular imports
158
171
  from signalwire.core.swml_service import SWMLService
@@ -214,7 +227,24 @@ class AgentBase(
214
227
 
215
228
  # Initialize session manager
216
229
  self._session_manager = SessionManager(token_expiry_secs=token_expiry_secs)
217
-
230
+
231
+ # Webhook signature validation (porting-sdk/webhooks.md).
232
+ # Resolution order: explicit constructor arg → SIGNALWIRE_SIGNING_KEY env.
233
+ # When unset, web_mixin._register_routes does NOT mount the validator
234
+ # and the SDK logs a one-time WARNING so users notice in production.
235
+ self.signing_key = signing_key or os.environ.get("SIGNALWIRE_SIGNING_KEY")
236
+ self._trust_proxy_for_signature = trust_proxy_for_signature
237
+ if self.signing_key:
238
+ self.log.info("webhook_signature_validation_enabled")
239
+ else:
240
+ self.log.warning(
241
+ "webhook_signature_validation_disabled",
242
+ message=(
243
+ "[signalwire] webhook signature validation is disabled — "
244
+ "set signing_key or SIGNALWIRE_SIGNING_KEY to enable"
245
+ ),
246
+ )
247
+
218
248
  # URL override variables
219
249
  self._web_hook_url_override = None
220
250
  self._post_prompt_url_override = None
@@ -17,11 +17,14 @@ import contextvars
17
17
  from typing import Optional, Dict, Any, Callable
18
18
  from urllib.parse import urlparse, urlunparse
19
19
 
20
- from fastapi import FastAPI, APIRouter, Request, Response
20
+ from fastapi import Depends, FastAPI, APIRouter, Request, Response
21
21
  from fastapi.middleware.cors import CORSMiddleware
22
22
 
23
23
  from signalwire.core.logging_config import get_execution_mode
24
24
  from signalwire.core.function_result import FunctionResult
25
+ from signalwire.core.security.webhook_middleware import (
26
+ make_webhook_validation_dependency,
27
+ )
25
28
 
26
29
  # Per-request proxy URL to avoid race conditions in concurrent async contexts
27
30
  _request_proxy_url = contextvars.ContextVar('_request_proxy_url', default=None)
@@ -343,22 +346,37 @@ class WebMixin:
343
346
  def _register_routes(self, router):
344
347
  """
345
348
  Register routes for this agent
346
-
347
- This method ensures proper route registration by handling the routes
349
+
350
+ This method ensures proper route registration by handling the routes
348
351
  directly in AgentBase rather than inheriting from SWMLService.
349
-
352
+
350
353
  Args:
351
354
  router: FastAPI router to register routes with
352
355
  """
353
356
  # Health check endpoints are now registered directly on the main app
354
-
357
+
358
+ # Build webhook signature validation dependency once if signing_key is set.
359
+ # See porting-sdk/webhooks.md and signalwire.core.security.webhook_middleware.
360
+ # When unset, signed_post_deps stays empty and routes register without it.
361
+ signed_post_deps = []
362
+ if getattr(self, "signing_key", None):
363
+ sig_dep = make_webhook_validation_dependency(
364
+ self.signing_key,
365
+ trust_proxy=getattr(self, "_trust_proxy_for_signature", False),
366
+ )
367
+ signed_post_deps = [Depends(sig_dep)]
368
+
355
369
  # Root endpoint (handles both with and without trailing slash)
356
370
  @router.get("/")
357
- @router.post("/")
358
- async def handle_root(request: Request, response: Response):
359
- """Handle GET/POST requests to the root endpoint"""
371
+ async def handle_root_get(request: Request, response: Response):
372
+ """Handle GET requests to the root endpoint"""
360
373
  return await self._handle_root_request(request)
361
-
374
+
375
+ @router.post("/", dependencies=signed_post_deps)
376
+ async def handle_root_post(request: Request, response: Response):
377
+ """Handle POST requests to the root endpoint (signature-validated when signing_key is set)"""
378
+ return await self._handle_root_request(request)
379
+
362
380
  # Debug endpoint - Both versions
363
381
  @router.get("/debug")
364
382
  @router.get("/debug/")
@@ -367,23 +385,31 @@ class WebMixin:
367
385
  async def handle_debug(request: Request):
368
386
  """Handle GET/POST requests to the debug endpoint"""
369
387
  return await self._handle_debug_request(request)
370
-
371
- # SWAIG endpoint - Both versions
388
+
389
+ # SWAIG endpoint - Both versions
372
390
  @router.get("/swaig")
373
391
  @router.get("/swaig/")
374
- @router.post("/swaig")
375
- @router.post("/swaig/")
376
- async def handle_swaig(request: Request, response: Response):
377
- """Handle GET/POST requests to the SWAIG endpoint"""
392
+ async def handle_swaig_get(request: Request, response: Response):
393
+ """Handle GET requests to the SWAIG endpoint"""
378
394
  return await self._handle_swaig_request(request, response)
379
-
395
+
396
+ @router.post("/swaig", dependencies=signed_post_deps)
397
+ @router.post("/swaig/", dependencies=signed_post_deps)
398
+ async def handle_swaig_post(request: Request, response: Response):
399
+ """Handle POST requests to the SWAIG endpoint (signature-validated when signing_key is set)"""
400
+ return await self._handle_swaig_request(request, response)
401
+
380
402
  # Post prompt endpoint - Both versions
381
403
  @router.get("/post_prompt")
382
404
  @router.get("/post_prompt/")
383
- @router.post("/post_prompt")
384
- @router.post("/post_prompt/")
385
- async def handle_post_prompt(request: Request):
386
- """Handle GET/POST requests to the post_prompt endpoint"""
405
+ async def handle_post_prompt_get(request: Request):
406
+ """Handle GET requests to the post_prompt endpoint"""
407
+ return await self._handle_post_prompt_request(request)
408
+
409
+ @router.post("/post_prompt", dependencies=signed_post_deps)
410
+ @router.post("/post_prompt/", dependencies=signed_post_deps)
411
+ async def handle_post_prompt_post(request: Request):
412
+ """Handle POST requests to the post_prompt endpoint (signature-validated when signing_key is set)"""
387
413
  return await self._handle_post_prompt_request(request)
388
414
 
389
415
  # Check for input endpoint - Both versions
@@ -0,0 +1,26 @@
1
+ """
2
+ Copyright (c) 2025 SignalWire
3
+
4
+ This file is part of the SignalWire SDK.
5
+
6
+ Licensed under the MIT License.
7
+ See LICENSE file in the project root for full license information.
8
+ """
9
+
10
+ from signalwire.core.security.webhook_validator import (
11
+ validate_request,
12
+ validate_webhook_signature,
13
+ )
14
+ from signalwire.core.security.webhook_middleware import (
15
+ SIGNALWIRE_SIGNATURE_HEADER,
16
+ TWILIO_COMPAT_SIGNATURE_HEADER,
17
+ make_webhook_validation_dependency,
18
+ )
19
+
20
+ __all__ = [
21
+ "validate_request",
22
+ "validate_webhook_signature",
23
+ "make_webhook_validation_dependency",
24
+ "SIGNALWIRE_SIGNATURE_HEADER",
25
+ "TWILIO_COMPAT_SIGNATURE_HEADER",
26
+ ]
@@ -0,0 +1,164 @@
1
+ """
2
+ FastAPI middleware / dependency for SignalWire webhook signature validation.
3
+
4
+ Copyright (c) 2025 SignalWire. Licensed under the MIT License.
5
+ See LICENSE file in the project root for full license information.
6
+
7
+ This module ships a small, framework-aware adapter around
8
+ :func:`signalwire.core.security.webhook_validator.validate_webhook_signature`.
9
+
10
+ Why a custom dependency rather than a vanilla ``Depends`` on ``request.body()``?
11
+
12
+ - We MUST capture the raw bytes BEFORE FastAPI's JSON parser consumes the
13
+ stream — re-serialization changes whitespace and key order, which breaks
14
+ the Scheme A digest. The dependency stashes the raw body on
15
+ ``request.state.raw_body`` so the downstream handler can re-parse without
16
+ re-reading the stream.
17
+ - Reverse-proxy / ngrok deployments need the URL the platform POSTed to,
18
+ which differs from the URL the SDK sees. The dependency honors
19
+ ``X-Forwarded-Proto`` / ``X-Forwarded-Host`` when ``trust_proxy=True``,
20
+ plus the ``SWML_PROXY_URL_BASE`` env var, with ``request.url`` as last
21
+ resort.
22
+ - The legacy cXML/Compatibility scheme used the ``X-Twilio-Signature``
23
+ header. We accept it as an alias of ``X-SignalWire-Signature`` so users
24
+ migrating from the legacy SDK can keep their callers unchanged.
25
+
26
+ Usage::
27
+
28
+ from signalwire.core.security.webhook_middleware import (
29
+ make_webhook_validation_dependency,
30
+ )
31
+
32
+ dep = make_webhook_validation_dependency(signing_key="PSK...")
33
+
34
+ @app.post("/webhook", dependencies=[Depends(dep)])
35
+ async def webhook(request: Request):
36
+ body = request.state.raw_body # bytes; re-parse if you need JSON
37
+ """
38
+
39
+ from __future__ import annotations
40
+
41
+ import os
42
+ from typing import Awaitable, Callable, Optional
43
+
44
+ from fastapi import HTTPException, Request, Response, status
45
+
46
+ from signalwire.core.security.webhook_validator import validate_webhook_signature
47
+
48
+
49
+ SIGNALWIRE_SIGNATURE_HEADER = "x-signalwire-signature"
50
+ TWILIO_COMPAT_SIGNATURE_HEADER = "x-twilio-signature"
51
+
52
+
53
+ def _reconstruct_url(request: Request, *, trust_proxy: bool) -> str:
54
+ """Rebuild the public URL SignalWire POSTed to.
55
+
56
+ Resolution order (highest priority first):
57
+
58
+ 1. ``SWML_PROXY_URL_BASE`` env var (joined with the request path + query).
59
+ 2. ``X-Forwarded-Proto`` / ``X-Forwarded-Host`` headers, if
60
+ ``trust_proxy=True`` and both headers are present.
61
+ 3. ``request.url`` (FastAPI's view of the URL).
62
+ """
63
+ path_and_query = request.url.path
64
+ if request.url.query:
65
+ path_and_query = f"{path_and_query}?{request.url.query}"
66
+
67
+ proxy_base = os.environ.get("SWML_PROXY_URL_BASE")
68
+ if proxy_base:
69
+ return f"{proxy_base.rstrip('/')}{path_and_query}"
70
+
71
+ if trust_proxy:
72
+ fwd_host = request.headers.get("x-forwarded-host")
73
+ fwd_proto = request.headers.get("x-forwarded-proto", "https")
74
+ if fwd_host:
75
+ return f"{fwd_proto}://{fwd_host}{path_and_query}"
76
+
77
+ return str(request.url)
78
+
79
+
80
+ def _extract_signature_header(request: Request) -> Optional[str]:
81
+ """Return the SignalWire signature header (or ``X-Twilio-Signature`` alias)."""
82
+ sig = request.headers.get(SIGNALWIRE_SIGNATURE_HEADER)
83
+ if sig is None:
84
+ sig = request.headers.get(TWILIO_COMPAT_SIGNATURE_HEADER)
85
+ return sig
86
+
87
+
88
+ def make_webhook_validation_dependency(
89
+ signing_key: str,
90
+ *,
91
+ trust_proxy: bool = False,
92
+ ) -> Callable[[Request, Response], Awaitable[Optional[Response]]]:
93
+ """Build a FastAPI dependency that enforces signature validation.
94
+
95
+ The returned coroutine:
96
+
97
+ 1. Reads ``await request.body()`` and stashes the bytes on
98
+ ``request.state.raw_body``.
99
+ 2. Pulls the ``X-SignalWire-Signature`` header (or the Twilio alias).
100
+ 3. Reconstructs the public URL (proxy headers / env / fallback).
101
+ 4. Calls :func:`validate_webhook_signature`.
102
+ 5. On invalid signature: raises ``HTTPException(403)`` to short-circuit
103
+ the handler. FastAPI's ``dependencies=[Depends(...)]`` only honors
104
+ short-circuiting via raised exceptions — returning a Response from a
105
+ dependency does not stop the endpoint.
106
+ 6. On valid: returns ``None`` so the handler runs as normal.
107
+
108
+ Args:
109
+ signing_key: The customer's Signing Key. Required, non-empty.
110
+ trust_proxy: If True, honor ``X-Forwarded-Proto`` / ``X-Forwarded-Host``
111
+ when reconstructing the URL. Default False — proxy headers are
112
+ spoofable, so opt in only when you control the proxy.
113
+
114
+ Returns:
115
+ Async callable suitable for ``Depends(...)``.
116
+
117
+ Raises:
118
+ ValueError: at construction time if ``signing_key`` is empty.
119
+ """
120
+ if not signing_key:
121
+ raise ValueError("signing_key is required")
122
+
123
+ def _forbidden() -> None:
124
+ # Single canonical 403 short-circuit. No body detail (would leak
125
+ # which branch failed); validators MUST NOT log scheme details.
126
+ raise HTTPException(status_code=status.HTTP_403_FORBIDDEN)
127
+
128
+ async def dependency(request: Request, response: Response) -> None:
129
+ # Capture raw body BEFORE any other consumer reads the stream.
130
+ # request.body() caches internally so subsequent calls are safe.
131
+ raw_bytes = await request.body()
132
+ request.state.raw_body = raw_bytes
133
+ try:
134
+ raw_body_str = raw_bytes.decode("utf-8")
135
+ except UnicodeDecodeError:
136
+ # Non-UTF-8 body cannot match an HMAC over UTF-8 input.
137
+ _forbidden()
138
+
139
+ signature = _extract_signature_header(request)
140
+ if not signature:
141
+ _forbidden()
142
+
143
+ url = _reconstruct_url(request, trust_proxy=trust_proxy)
144
+
145
+ try:
146
+ ok = validate_webhook_signature(signing_key, signature, url, raw_body_str)
147
+ except (TypeError, ValueError):
148
+ # Programming errors or non-string body — treat as invalid for the
149
+ # request without leaking which branch tripped.
150
+ _forbidden()
151
+ return
152
+
153
+ if not ok:
154
+ _forbidden()
155
+ # Valid — fall through and let the handler run.
156
+
157
+ return dependency
158
+
159
+
160
+ __all__ = [
161
+ "make_webhook_validation_dependency",
162
+ "SIGNALWIRE_SIGNATURE_HEADER",
163
+ "TWILIO_COMPAT_SIGNATURE_HEADER",
164
+ ]