[{"data":1,"prerenderedAt":4492},["ShallowReactive",2],{"navigation_docs":3,"-integrations-and-extensibility-webhooks-verifying-signatures":1582,"-integrations-and-extensibility-webhooks-verifying-signatures-surround":4487},[4,237,583,622,898,988,1106,1240,1578],{"title":5,"path":6,"stem":7,"children":8,"page":84},"Getting Started","/getting-started","01.getting-started",[9,15,226,231],{"title":10,"path":11,"stem":12,"children":13},"Creating Your Account","/getting-started/creating-your-account","01.getting-started/00.creating-your-account/index",[14],{"title":10,"path":11,"stem":12},{"title":16,"order":17,"path":18,"stem":19,"children":20,"page":84},"Admin Onboarding: Getting Started",1,"/getting-started/admin-onboarding-getting-started","01.getting-started/01.admin-onboarding-getting-started",[21,25,29,33,40,44,51,85,134,147,158,162,175,179,188,205,213,219],{"title":22,"path":23,"stem":24},"Setting the Stage: How Predictability becomes Reality in DevStride","/getting-started/admin-onboarding-getting-started/setting-the-stage","01.getting-started/01.admin-onboarding-getting-started/01.setting-the-stage",{"title":26,"path":27,"stem":28},"Admin Onboarding - Getting Started","/getting-started/admin-onboarding-getting-started/admin-onboarding-guide","01.getting-started/01.admin-onboarding-getting-started/02.admin-onboarding-guide",{"title":30,"path":31,"stem":32},"Basic Terms","/getting-started/admin-onboarding-getting-started/basic-terms","01.getting-started/01.admin-onboarding-getting-started/03.basic-terms",{"title":34,"path":35,"stem":36,"children":37},"Setting Up Users and Teams","/getting-started/admin-onboarding-getting-started/setting-up-users-and-teams","01.getting-started/01.admin-onboarding-getting-started/04.setting-up-users-and-teams/index",[38],{"title":39,"path":35,"stem":36},"Admin Onboarding - Setting up Users and Teams",{"title":41,"path":42,"stem":43},"Admin Onboarding - Configuring Your Work Model","/getting-started/admin-onboarding-getting-started/configuring-your-work-model","01.getting-started/01.admin-onboarding-getting-started/05.configuring-your-work-model",{"title":45,"path":46,"stem":47,"children":48},"Map Value: WorkStreams","/getting-started/admin-onboarding-getting-started/map-value-workstreams","01.getting-started/01.admin-onboarding-getting-started/06.map-value-workstreams/index",[49],{"title":50,"path":46,"stem":47},"Admin Onboarding: Understanding Workstreams",{"title":52,"path":53,"stem":54,"children":55,"page":84},"Items","/getting-started/admin-onboarding-getting-started/items","01.getting-started/01.admin-onboarding-getting-started/07.items",[56,60,67,73,80],{"title":57,"path":58,"stem":59},"Setting up Your Item Type Hierarchy","/getting-started/admin-onboarding-getting-started/items/setting-up-item-type-hierarchy","01.getting-started/01.admin-onboarding-getting-started/07.items/01.setting-up-item-type-hierarchy",{"title":61,"path":62,"stem":63,"children":64},"Items: Setting Up Custom Fields","/getting-started/admin-onboarding-getting-started/items/setting-up-custom-fields","01.getting-started/01.admin-onboarding-getting-started/07.items/02.setting-up-custom-fields/index",[65],{"title":66,"path":62,"stem":63},"Admin Onboarding - Setting up Custom Fields",{"title":68,"path":69,"stem":70,"children":71},"Admin Onboarding - Setting up Tags","/getting-started/admin-onboarding-getting-started/items/setting-up-tags","01.getting-started/01.admin-onboarding-getting-started/07.items/03.setting-up-tags/index",[72],{"title":68,"path":69,"stem":70},{"title":74,"path":75,"stem":76,"children":77},"Items: Creating and Managing Forms","/getting-started/admin-onboarding-getting-started/items/creating-and-managing-forms","01.getting-started/01.admin-onboarding-getting-started/07.items/04.creating-and-managing-forms/index",[78],{"title":79,"path":75,"stem":76},"Admin Onboarding - Creating and Managing Forms",{"title":81,"path":82,"stem":83},"Create Items with AI","/getting-started/admin-onboarding-getting-started/items/create-items-with-ai","01.getting-started/01.admin-onboarding-getting-started/07.items/05.create-items-with-ai",false,{"title":86,"path":87,"stem":88,"children":89,"page":84},"Item Workspace Tour","/getting-started/admin-onboarding-getting-started/item-workspace-tour","01.getting-started/01.admin-onboarding-getting-started/08.item-workspace-tour",[90,94,98,102,106,110,114,118,122,126,130],{"title":91,"path":92,"stem":93},"Admin Onboarding - Item Workspace Header","/getting-started/admin-onboarding-getting-started/item-workspace-tour/header","01.getting-started/01.admin-onboarding-getting-started/08.item-workspace-tour/01.header",{"title":95,"path":96,"stem":97},"Admin Onboarding - Item Workspace Tabs","/getting-started/admin-onboarding-getting-started/item-workspace-tour/tabs","01.getting-started/01.admin-onboarding-getting-started/08.item-workspace-tour/02.tabs",{"title":99,"path":100,"stem":101},"Item Workspace Quick Links","/getting-started/admin-onboarding-getting-started/item-workspace-tour/quick-links","01.getting-started/01.admin-onboarding-getting-started/08.item-workspace-tour/03.quick-links",{"title":103,"path":104,"stem":105},"Admin Onboarding - Item Workspace - Right Bar: Team through Status","/getting-started/admin-onboarding-getting-started/item-workspace-tour/right-bar-team-through-status","01.getting-started/01.admin-onboarding-getting-started/08.item-workspace-tour/04.right-bar-team-through-status",{"title":107,"path":108,"stem":109},"Admin Onboarding - Item Workspace - Right Bar: Point Estimates","/getting-started/admin-onboarding-getting-started/item-workspace-tour/right-bar-point-estimates","01.getting-started/01.admin-onboarding-getting-started/08.item-workspace-tour/05.right-bar-point-estimates",{"title":111,"path":112,"stem":113},"Admin Onboarding - Item Workspace - Right Bar: Time Estimates","/getting-started/admin-onboarding-getting-started/item-workspace-tour/right-bar-time-estimates","01.getting-started/01.admin-onboarding-getting-started/08.item-workspace-tour/06.right-bar-time-estimates",{"title":115,"path":116,"stem":117},"Admin Onboarding - Item Workspace -Right Bar: Best Practice time estimates","/getting-started/admin-onboarding-getting-started/item-workspace-tour/right-bar-best-practice-time-estimates","01.getting-started/01.admin-onboarding-getting-started/08.item-workspace-tour/07.right-bar-best-practice-time-estimates",{"title":119,"path":120,"stem":121},"Admin Onboarding - Item Workspace - Right Bar: Time Spent","/getting-started/admin-onboarding-getting-started/item-workspace-tour/right-bar-time-spent","01.getting-started/01.admin-onboarding-getting-started/08.item-workspace-tour/08.right-bar-time-spent",{"title":123,"path":124,"stem":125},"Admin Onboarding - Item Workspace - Right Bar: Priority","/getting-started/admin-onboarding-getting-started/item-workspace-tour/right-bar-priority","01.getting-started/01.admin-onboarding-getting-started/08.item-workspace-tour/09.right-bar-priority",{"title":127,"path":128,"stem":129},"Admin Onboarding - Item Workspace - Right Bar: Start Dates and Due Dates","/getting-started/admin-onboarding-getting-started/item-workspace-tour/right-bar-start-dates-and-due-dates","01.getting-started/01.admin-onboarding-getting-started/08.item-workspace-tour/10.right-bar-start-dates-and-due-dates",{"title":131,"path":132,"stem":133},"Admin Onboarding - Item Workspace - Right Bar: Progress Tracking","/getting-started/admin-onboarding-getting-started/item-workspace-tour/right-bar-progress-tracking","01.getting-started/01.admin-onboarding-getting-started/08.item-workspace-tour/11.right-bar-progress-tracking",{"title":135,"path":136,"stem":137,"children":138,"page":84},"Creating and Using Statuses","/getting-started/admin-onboarding-getting-started/creating-and-using-statuses","01.getting-started/01.admin-onboarding-getting-started/09.creating-and-using-statuses",[139,143],{"title":140,"path":141,"stem":142},"Admin Onboarding - Setting Up Workflow Statuses","/getting-started/admin-onboarding-getting-started/creating-and-using-statuses/setting-up-workflow-statuses","01.getting-started/01.admin-onboarding-getting-started/09.creating-and-using-statuses/01.setting-up-workflow-statuses",{"title":144,"path":145,"stem":146},"Admin Onboarding - Create Custom Status Collections","/getting-started/admin-onboarding-getting-started/creating-and-using-statuses/create-custom-status-collections","01.getting-started/01.admin-onboarding-getting-started/09.creating-and-using-statuses/02.create-custom-status-collections",{"title":148,"path":149,"stem":150,"children":151},"Populating and Managing WorkStreams","/getting-started/admin-onboarding-getting-started/populating-and-managing-workstreams","01.getting-started/01.admin-onboarding-getting-started/10.populating-and-managing-workstreams/index",[152,154],{"title":153,"path":149,"stem":150},"Admin Onboarding - Populating and Managing Workstreams",{"title":155,"path":156,"stem":157},"Import Templates","/getting-started/admin-onboarding-getting-started/populating-and-managing-workstreams/import-templates","01.getting-started/01.admin-onboarding-getting-started/10.populating-and-managing-workstreams/01.import-templates",{"title":159,"path":160,"stem":161},"Boards: Managing Workflow and Statuses","/getting-started/admin-onboarding-getting-started/boards-managing-workflow-and-statuses","01.getting-started/01.admin-onboarding-getting-started/11.boards-managing-workflow-and-statuses",{"title":163,"path":164,"stem":165,"children":166,"page":84},"Creating Cadences","/getting-started/admin-onboarding-getting-started/creating-cadences","01.getting-started/01.admin-onboarding-getting-started/12.creating-cadences",[167,171],{"title":168,"path":169,"stem":170},"Admin Onboarding - Organizing the When: Understanding Planning Cadences","/getting-started/admin-onboarding-getting-started/creating-cadences/understanding-planning-cadences","01.getting-started/01.admin-onboarding-getting-started/12.creating-cadences/01.understanding-planning-cadences",{"title":172,"path":173,"stem":174},"Admin Onboarding - Setting Up Cadences and Timeboxes","/getting-started/admin-onboarding-getting-started/creating-cadences/setting-up-cadences-and-timeboxes","01.getting-started/01.admin-onboarding-getting-started/12.creating-cadences/02.setting-up-cadences-and-timeboxes",{"title":176,"path":177,"stem":178},"Track Progress - Gantt Charts","/getting-started/admin-onboarding-getting-started/track-progress-gantt-charts","01.getting-started/01.admin-onboarding-getting-started/13.track-progress-gantt-charts",{"title":180,"path":181,"stem":182,"children":183,"page":84},"Using AI in DevStride","/getting-started/admin-onboarding-getting-started/using-ai-in-devstride","01.getting-started/01.admin-onboarding-getting-started/14.using-ai-in-devstride",[184],{"title":185,"path":186,"stem":187},"Creating Items with AI","/getting-started/admin-onboarding-getting-started/using-ai-in-devstride/create-items-with-ai","01.getting-started/01.admin-onboarding-getting-started/14.using-ai-in-devstride/01.create-items-with-ai",{"title":189,"path":190,"stem":191,"children":192,"page":84},"More on Permissions","/getting-started/admin-onboarding-getting-started/permissions","01.getting-started/01.admin-onboarding-getting-started/15.permissions",[193,197,201],{"title":194,"path":195,"stem":196},"Understanding Permissions","/getting-started/admin-onboarding-getting-started/permissions/understanding-permissions","01.getting-started/01.admin-onboarding-getting-started/15.permissions/01.understanding-permissions",{"title":198,"path":199,"stem":200},"Roles & Permissions","/getting-started/admin-onboarding-getting-started/permissions/roles-and-permissions","01.getting-started/01.admin-onboarding-getting-started/15.permissions/02.roles-and-permissions",{"title":202,"path":203,"stem":204},"Sharing & Access","/getting-started/admin-onboarding-getting-started/permissions/sharing-and-access","01.getting-started/01.admin-onboarding-getting-started/15.permissions/03.sharing-and-access",{"title":206,"path":207,"stem":208,"children":209,"page":84},"API Keys","/getting-started/admin-onboarding-getting-started/api-keys","01.getting-started/01.admin-onboarding-getting-started/16.api-keys",[210],{"title":206,"path":211,"stem":212},"/getting-started/admin-onboarding-getting-started/api-keys/api-keys","01.getting-started/01.admin-onboarding-getting-started/16.api-keys/01.api-keys",{"title":214,"path":215,"stem":216,"children":217},"Pulling it all together: Planned Value to Delivery","/getting-started/admin-onboarding-getting-started/overall-flow-and-best-practices","01.getting-started/01.admin-onboarding-getting-started/17.overall-flow-and-best-practices/index",[218],{"title":214,"path":215,"stem":216},{"title":220,"path":221,"stem":222,"children":223},"Best Practices for Project Management in DevStride","/getting-started/admin-onboarding-getting-started/best-practices-for-project-management","01.getting-started/01.admin-onboarding-getting-started/18.best-practices-for-project-management/index",[224],{"title":225,"path":221,"stem":222},"Handling incomplete items from Sprint to Sprint",{"title":30,"path":227,"stem":228,"children":229},"/getting-started/basic-terms","01.getting-started/02.basic-terms/index",[230],{"title":30,"path":227,"stem":228},{"title":232,"path":233,"stem":234,"children":235},"Keyboard Shortcuts","/getting-started/keyboard-shortcuts","01.getting-started/03.keyboard-shortcuts/index",[236],{"title":232,"path":233,"stem":234},{"title":238,"path":239,"stem":240,"children":241,"page":84},"Major Modules","/major-modules","02.major-modules",[242,300,354,375,441,502,530],{"title":243,"order":244,"path":245,"stem":246,"children":247,"page":84},"Map Value: Workstreams and Work Items",2,"/major-modules/workstreams-and-work-items","02.major-modules/01.workstreams-and-work-items",[248,252,256,259,263,267,271,275,278],{"title":249,"path":250,"stem":251},"Creating Items","/major-modules/workstreams-and-work-items/creating-work-items","02.major-modules/01.workstreams-and-work-items/01.creating-work-items",{"title":253,"path":254,"stem":255},"The Items Table","/major-modules/workstreams-and-work-items/manage-items-table","02.major-modules/01.workstreams-and-work-items/02.manage-items-table",{"title":185,"path":257,"stem":258},"/major-modules/workstreams-and-work-items/ai-item-creation","02.major-modules/01.workstreams-and-work-items/03.ai-item-creation",{"title":260,"path":261,"stem":262},"Shared Lists","/major-modules/workstreams-and-work-items/published-lists","02.major-modules/01.workstreams-and-work-items/04.published-lists",{"title":264,"path":265,"stem":266},"Using the Map","/major-modules/workstreams-and-work-items/using-the-map","02.major-modules/01.workstreams-and-work-items/05.using-the-map",{"title":268,"path":269,"stem":270},"Dependency Graph","/major-modules/workstreams-and-work-items/dependency-graph","02.major-modules/01.workstreams-and-work-items/06.dependency-graph",{"title":272,"path":273,"stem":274},"Workstream & Item Permissions","/major-modules/workstreams-and-work-items/workstream-and-item-permissions","02.major-modules/01.workstreams-and-work-items/07.workstream-and-item-permissions",{"title":155,"path":276,"stem":277},"/major-modules/workstreams-and-work-items/import-templates","02.major-modules/01.workstreams-and-work-items/08.import-templates",{"title":279,"order":280,"path":281,"stem":282,"children":283,"page":84},"Workspace Capabilities",8,"/major-modules/workstreams-and-work-items/workspace-capabilities","02.major-modules/01.workstreams-and-work-items/09.workspace-capabilities",[284,288,292,296],{"title":285,"path":286,"stem":287},"Explore the Workspace","/major-modules/workstreams-and-work-items/workspace-capabilities/explore-the-workspace","02.major-modules/01.workstreams-and-work-items/09.workspace-capabilities/01.explore-the-workspace",{"title":289,"path":290,"stem":291},"Workspace Editor & Collaboration","/major-modules/workstreams-and-work-items/workspace-capabilities/workspace-sidebar","02.major-modules/01.workstreams-and-work-items/09.workspace-capabilities/02.workspace-sidebar",{"title":293,"path":294,"stem":295},"Themes & Appearance","/major-modules/workstreams-and-work-items/workspace-capabilities/themes-and-appearance","02.major-modules/01.workstreams-and-work-items/09.workspace-capabilities/03.themes-and-appearance",{"title":297,"path":298,"stem":299},"Getting Help & Support","/major-modules/workstreams-and-work-items/workspace-capabilities/getting-help-and-support","02.major-modules/01.workstreams-and-work-items/09.workspace-capabilities/04.getting-help-and-support",{"title":301,"order":302,"path":303,"stem":304,"children":305,"page":84},"Plan Delivery: Boards",3,"/major-modules/boards","02.major-modules/02.boards",[306,310,314,318,322,326,330,334,338,342,346,350],{"title":307,"path":308,"stem":309},"How DevStride manages the When: Perpetual vs. Cycle-based Boards","/major-modules/boards/how-devstride-manages-the-when","02.major-modules/02.boards/01.how-devstride-manages-the-when",{"title":311,"path":312,"stem":313},"Working Group Folders: Organizing the Who of the work","/major-modules/boards/working-group-folders","02.major-modules/02.boards/02.working-group-folders",{"title":315,"path":316,"stem":317},"Understanding Board Views","/major-modules/boards/understanding-board-views","02.major-modules/02.boards/03.understanding-board-views",{"title":319,"path":320,"stem":321},"Import Stories and Epics into Boards","/major-modules/boards/import-stories-and-epics-into-boards","02.major-modules/02.boards/04.import-stories-and-epics-into-boards",{"title":323,"path":324,"stem":325},"Adding a Folder","/major-modules/boards/adding-a-folder","02.major-modules/02.boards/05.adding-a-folder",{"title":327,"path":328,"stem":329},"Manage and Edit Dependencies","/major-modules/boards/manage-and-edit-dependencies","02.major-modules/02.boards/06.manage-and-edit-dependencies",{"title":331,"path":332,"stem":333},"Creating Subfolders in DevStride","/major-modules/boards/creating-subfolders-in-devstride","02.major-modules/02.boards/07.creating-subfolders-in-devstride",{"title":335,"path":336,"stem":337},"Setting up and Using Perpetual Boards","/major-modules/boards/setting-up-and-using-perpetual-boards","02.major-modules/02.boards/08.setting-up-and-using-perpetual-boards",{"title":339,"path":340,"stem":341},"Using Cycle Boards","/major-modules/boards/using-cycle-boards","02.major-modules/02.boards/09.using-cycle-boards",{"title":343,"path":344,"stem":345},"Track and Move Work with Board Views","/major-modules/boards/track-and-move-work-with-board-views","02.major-modules/02.boards/10.track-and-move-work-with-board-views",{"title":347,"path":348,"stem":349},"Boards and WIP Limiters","/major-modules/boards/boards-and-wip-limiters","02.major-modules/02.boards/11.boards-and-wip-limiters",{"title":351,"path":352,"stem":353},"Board & Folder Permissions","/major-modules/boards/board-and-folder-permissions","02.major-modules/02.boards/12.board-and-folder-permissions",{"title":355,"path":356,"stem":357,"children":358,"page":84},"Track Progress: Gantts","/major-modules/track-progress-gantts","02.major-modules/03.track-progress-gantts",[359,363,367,371],{"title":360,"path":361,"stem":362},"Track Progress: Setting Up Gantt Charts","/major-modules/track-progress-gantts/setting-up-gantt-charts","02.major-modules/03.track-progress-gantts/01.setting-up-gantt-charts",{"title":364,"path":365,"stem":366},"Viewing Gantt Charts","/major-modules/track-progress-gantts/viewing-gantt-charts","02.major-modules/03.track-progress-gantts/02.viewing-gantt-charts",{"title":368,"path":369,"stem":370},"Using Gantt Charts: Navigating the timeline","/major-modules/track-progress-gantts/navigating-the-timeline","02.major-modules/03.track-progress-gantts/03.navigating-the-timeline",{"title":372,"path":373,"stem":374},"Using Gantt Charts: Navigating the table","/major-modules/track-progress-gantts/navigating-the-table","02.major-modules/03.track-progress-gantts/04.navigating-the-table",{"title":376,"order":377,"path":378,"stem":379,"children":380,"page":84},"Measure Performance: Reports",4,"/major-modules/measure-performance-reports","02.major-modules/04.measure-performance-reports",[381,385,389,393,397,401,405,409,413,417,421,425,429,433,437],{"title":382,"path":383,"stem":384},"Reports Overview","/major-modules/measure-performance-reports/viewing-optimization","02.major-modules/04.measure-performance-reports/01.viewing-optimization",{"title":386,"path":387,"stem":388},"Using AI for powerful reports","/major-modules/measure-performance-reports/using-ai-for-powerful-reports","02.major-modules/04.measure-performance-reports/02.using-ai-for-powerful-reports",{"title":390,"path":391,"stem":392},"Report Customization Options","/major-modules/measure-performance-reports/report-filters-and-options","02.major-modules/04.measure-performance-reports/03.report-filters-and-options",{"title":394,"path":395,"stem":396},"Report: Current Progress","/major-modules/measure-performance-reports/current-progress","02.major-modules/04.measure-performance-reports/04.current-progress",{"title":398,"path":399,"stem":400},"Report: Trending Progress","/major-modules/measure-performance-reports/trending-progress","02.major-modules/04.measure-performance-reports/05.trending-progress",{"title":402,"path":403,"stem":404},"Report: Cycle Time","/major-modules/measure-performance-reports/cycle-time","02.major-modules/04.measure-performance-reports/06.cycle-time",{"title":406,"path":407,"stem":408},"Report: Throughput","/major-modules/measure-performance-reports/throughput","02.major-modules/04.measure-performance-reports/07.throughput",{"title":410,"path":411,"stem":412},"Report: Cumulative Flow","/major-modules/measure-performance-reports/cumulative-flow","02.major-modules/04.measure-performance-reports/08.cumulative-flow",{"title":414,"path":415,"stem":416},"Report: Velocity","/major-modules/measure-performance-reports/velocity","02.major-modules/04.measure-performance-reports/09.velocity",{"title":418,"path":419,"stem":420},"Report: Burn Up","/major-modules/measure-performance-reports/burn-up","02.major-modules/04.measure-performance-reports/10.burn-up",{"title":422,"path":423,"stem":424},"Report: Burn Down","/major-modules/measure-performance-reports/burn-down","02.major-modules/04.measure-performance-reports/11.burn-down",{"title":426,"path":427,"stem":428},"Report: User Time Completed","/major-modules/measure-performance-reports/user-time-completed","02.major-modules/04.measure-performance-reports/12.user-time-completed",{"title":430,"path":431,"stem":432},"Report: Churn","/major-modules/measure-performance-reports/churn","02.major-modules/04.measure-performance-reports/13.churn",{"title":434,"path":435,"stem":436},"Report: Time Allocation Matrix","/major-modules/measure-performance-reports/time-allocation-matrix","02.major-modules/04.measure-performance-reports/14.time-allocation-matrix",{"title":438,"path":439,"stem":440},"Report: Cycle Carry-Forward","/major-modules/measure-performance-reports/cycle-carry-forward","02.major-modules/04.measure-performance-reports/15.cycle-carry-forward",{"title":442,"order":280,"path":443,"stem":444,"children":445,"page":84},"Service Desk","/major-modules/service-desk","02.major-modules/05.service-desk",[446,450,454,458,462,466,470,474,478,482,486,490,494,498],{"title":447,"path":448,"stem":449},"Overview","/major-modules/service-desk/overview","02.major-modules/05.service-desk/01.overview",{"title":451,"path":452,"stem":453},"Enabling Service Desk","/major-modules/service-desk/enabling-service-desk","02.major-modules/05.service-desk/02.enabling-service-desk",{"title":455,"path":456,"stem":457},"Email Channels & Setup","/major-modules/service-desk/email-channels","02.major-modules/05.service-desk/03.email-channels",{"title":459,"path":460,"stem":461},"Requesters & Companies","/major-modules/service-desk/requesters-and-companies","02.major-modules/05.service-desk/04.requesters-and-companies",{"title":463,"path":464,"stem":465},"Customer Support Portal","/major-modules/service-desk/customer-support-portal","02.major-modules/05.service-desk/05.customer-support-portal",{"title":467,"path":468,"stem":469},"Working a Ticket","/major-modules/service-desk/working-a-ticket","02.major-modules/05.service-desk/06.working-a-ticket",{"title":471,"path":472,"stem":473},"Request Forms","/major-modules/service-desk/request-forms","02.major-modules/05.service-desk/07.request-forms",{"title":475,"path":476,"stem":477},"The Suspended Queue","/major-modules/service-desk/suspended-messages","02.major-modules/05.service-desk/08.suspended-messages",{"title":479,"path":480,"stem":481},"Finding & Filtering Tickets","/major-modules/service-desk/finding-and-filtering-tickets","02.major-modules/05.service-desk/09.finding-and-filtering-tickets",{"title":483,"path":484,"stem":485},"Permissions","/major-modules/service-desk/permissions","02.major-modules/05.service-desk/10.permissions",{"title":487,"path":488,"stem":489},"Service Level Agreements (SLA)","/major-modules/service-desk/sla","02.major-modules/05.service-desk/10.sla",{"title":491,"path":492,"stem":493},"Time-Based Ticket Automations","/major-modules/service-desk/time-based-automations","02.major-modules/05.service-desk/11.time-based-automations",{"title":495,"path":496,"stem":497},"Reports","/major-modules/service-desk/reports","02.major-modules/05.service-desk/12.reports",{"title":499,"path":500,"stem":501},"How email replies find their ticket","/major-modules/service-desk/how-email-replies-find-their-ticket","02.major-modules/05.service-desk/13.how-email-replies-find-their-ticket",{"title":503,"path":504,"stem":505,"children":506,"page":84},"Templates","/major-modules/templates","02.major-modules/06.templates",[507,510,514,518,522,526],{"title":447,"path":508,"stem":509},"/major-modules/templates/overview","02.major-modules/06.templates/01.overview",{"title":511,"path":512,"stem":513},"Creating a template","/major-modules/templates/creating-a-template","02.major-modules/06.templates/02.creating-a-template",{"title":515,"path":516,"stem":517},"Variables","/major-modules/templates/variables","02.major-modules/06.templates/03.variables",{"title":519,"path":520,"stem":521},"Applying a template","/major-modules/templates/applying-a-template","02.major-modules/06.templates/04.applying-a-template",{"title":523,"path":524,"stem":525},"Sharing templates","/major-modules/templates/sharing-templates","02.major-modules/06.templates/05.sharing-templates",{"title":527,"path":528,"stem":529},"Template health","/major-modules/templates/template-health","02.major-modules/06.templates/06.template-health",{"title":531,"path":532,"stem":533,"children":534,"page":84},"Knowledge Base","/major-modules/knowledge-base","02.major-modules/07.knowledge-base",[535,539,543,547,551,555,559,563,567,571,575,579],{"title":536,"path":537,"stem":538},"Knowledge Base Overview","/major-modules/knowledge-base/overview","02.major-modules/07.knowledge-base/01.overview",{"title":540,"path":541,"stem":542},"Write and Publish Your First Article","/major-modules/knowledge-base/write-and-publish-your-first-article","02.major-modules/07.knowledge-base/02.write-and-publish-your-first-article",{"title":544,"path":545,"stem":546},"Organizing with Categories","/major-modules/knowledge-base/organizing-with-categories","02.major-modules/07.knowledge-base/03.organizing-with-categories",{"title":548,"path":549,"stem":550},"Who Can See an Article","/major-modules/knowledge-base/who-can-see-an-article","02.major-modules/07.knowledge-base/04.who-can-see-an-article",{"title":552,"path":553,"stem":554},"The Article Lifecycle and URLs","/major-modules/knowledge-base/article-lifecycle-and-urls","02.major-modules/07.knowledge-base/05.article-lifecycle-and-urls",{"title":556,"path":557,"stem":558},"Article History and Restore","/major-modules/knowledge-base/article-history-and-restore","02.major-modules/07.knowledge-base/06.article-history-and-restore",{"title":560,"path":561,"stem":562},"Searching and Browsing","/major-modules/knowledge-base/searching-and-browsing","02.major-modules/07.knowledge-base/07.searching-and-browsing",{"title":564,"path":565,"stem":566},"The Customer Help Center","/major-modules/knowledge-base/the-customer-help-center","02.major-modules/07.knowledge-base/08.the-customer-help-center",{"title":568,"path":569,"stem":570},"Sharing Articles and Categories","/major-modules/knowledge-base/sharing-articles-and-categories","02.major-modules/07.knowledge-base/09.sharing-articles-and-categories",{"title":572,"path":573,"stem":574},"Article Review Comments","/major-modules/knowledge-base/article-review-comments","02.major-modules/07.knowledge-base/10.article-review-comments",{"title":576,"path":577,"stem":578},"Knowledge Base Permissions","/major-modules/knowledge-base/knowledge-base-permissions","02.major-modules/07.knowledge-base/11.knowledge-base-permissions",{"title":580,"path":581,"stem":582},"Customer Comments on Articles","/major-modules/knowledge-base/customer-comments-on-articles","02.major-modules/07.knowledge-base/12.customer-comments-on-articles",{"title":584,"path":585,"stem":586,"children":587,"page":84},"Personal Workspace","/personal-workspace","03.personal-workspace",[588,594,608,619],{"title":589,"path":590,"stem":591,"children":592},"My Work","/personal-workspace/my-work","03.personal-workspace/01.my-work/index",[593],{"title":589,"path":590,"stem":591},{"title":595,"order":596,"path":597,"stem":598,"children":599,"page":84},"Notifications",7,"/personal-workspace/notifications","03.personal-workspace/02.notifications",[600,604],{"title":601,"path":602,"stem":603},"Notifications in DevStride","/personal-workspace/notifications/overview-of-notification-options","03.personal-workspace/02.notifications/01.overview-of-notification-options",{"title":605,"path":606,"stem":607},"Customizing Notification Settings","/personal-workspace/notifications/customizing-notification-settings-for-individuals-and-teams","03.personal-workspace/02.notifications/02.customizing-notification-settings-for-individuals-and-teams",{"title":609,"path":610,"stem":611,"children":612,"page":84},"My Account","/personal-workspace/my-account","03.personal-workspace/03.my-account",[613,616],{"title":447,"path":614,"stem":615},"/personal-workspace/my-account/overview","03.personal-workspace/03.my-account/01.overview",{"title":206,"path":617,"stem":618},"/personal-workspace/my-account/api-keys","03.personal-workspace/03.my-account/02.api-keys",{"title":232,"path":620,"stem":621},"/personal-workspace/keyboard-shortcuts","03.personal-workspace/04.keyboard-shortcuts",{"title":623,"path":624,"stem":625,"children":626,"page":84},"Workspace Administration","/workspace-administration","04.workspace-administration",[627,647,657,802,815,894],{"title":628,"path":629,"stem":630,"children":631,"page":84},"Manage Items Module","/workspace-administration/manage-items","04.workspace-administration/01.manage-items",[632,635,639,643],{"title":447,"path":633,"stem":634},"/workspace-administration/manage-items/overview","04.workspace-administration/01.manage-items/01.overview",{"title":636,"path":637,"stem":638},"Finding & Filtering Items","/workspace-administration/manage-items/finding-and-filtering-items","04.workspace-administration/01.manage-items/02.finding-and-filtering-items",{"title":640,"path":641,"stem":642},"Creating & Sharing Views","/workspace-administration/manage-items/creating-and-sharing-views","04.workspace-administration/01.manage-items/03.creating-and-sharing-views",{"title":644,"path":645,"stem":646},"Item Hierarchy","/workspace-administration/manage-items/item-hierarchy","04.workspace-administration/01.manage-items/04.item-hierarchy",{"title":648,"order":649,"path":650,"stem":651,"children":652,"page":84},"Automate Workflows",5,"/workspace-administration/automations","04.workspace-administration/02.automations",[653],{"title":654,"path":655,"stem":656},"Setting Up Basic Automations","/workspace-administration/automations/setting-up-basic-automations","04.workspace-administration/02.automations/01.setting-up-basic-automations",{"title":658,"order":659,"path":660,"stem":661,"children":662,"page":84},"Configure Settings",6,"/workspace-administration/settings","04.workspace-administration/03.settings",[663,708,745,754,798],{"title":664,"path":665,"stem":666,"children":667,"page":84},"Organization","/workspace-administration/settings/organization","04.workspace-administration/03.settings/01.organization",[668,672,676,680,684,688,692,696,700,704],{"title":669,"path":670,"stem":671},"Org Configuration","/workspace-administration/settings/organization/org-profile","04.workspace-administration/03.settings/01.organization/01.org-profile",{"title":673,"path":674,"stem":675},"Users, Roles & MFA","/workspace-administration/settings/organization/users-roles-and-mfa","04.workspace-administration/03.settings/01.organization/02.users-roles-and-mfa",{"title":677,"path":678,"stem":679},"Time Tracking Lock & Soft Close","/workspace-administration/settings/organization/time-tracking-lock","04.workspace-administration/03.settings/01.organization/03.time-tracking-lock",{"title":681,"path":682,"stem":683},"Training Sandboxes","/workspace-administration/settings/organization/training-sandboxes","04.workspace-administration/03.settings/01.organization/04.training-sandboxes",{"title":685,"path":686,"stem":687},"Training Sandbox Guide","/workspace-administration/settings/organization/training-sandbox-guide","04.workspace-administration/03.settings/01.organization/05.training-sandbox-guide",{"title":689,"path":690,"stem":691},"Personas","/workspace-administration/settings/organization/personas","04.workspace-administration/03.settings/01.organization/06.personas",{"title":693,"path":694,"stem":695},"Teams","/workspace-administration/settings/organization/teams","04.workspace-administration/03.settings/01.organization/07.teams",{"title":697,"path":698,"stem":699},"Import History","/workspace-administration/settings/organization/import-history","04.workspace-administration/03.settings/01.organization/08.import-history",{"title":701,"path":702,"stem":703},"Formatting Rules","/workspace-administration/settings/organization/formatting-rules","04.workspace-administration/03.settings/01.organization/09.formatting-rules",{"title":705,"path":706,"stem":707},"Subtask Statuses","/workspace-administration/settings/organization/subtask-statuses","04.workspace-administration/03.settings/01.organization/10.subtask-statuses",{"title":709,"path":710,"stem":711,"children":712,"page":84},"Data Model","/workspace-administration/settings/data-model","04.workspace-administration/03.settings/02.data-model",[713,717,721,725,729,733,737,741],{"title":714,"path":715,"stem":716},"Cadences","/workspace-administration/settings/data-model/cadences","04.workspace-administration/03.settings/02.data-model/01.cadences",{"title":718,"path":719,"stem":720},"Item Types","/workspace-administration/settings/data-model/item-types","04.workspace-administration/03.settings/02.data-model/02.item-types",{"title":722,"path":723,"stem":724},"Tags","/workspace-administration/settings/data-model/tags","04.workspace-administration/03.settings/02.data-model/03.tags",{"title":726,"path":727,"stem":728},"Custom Fields","/workspace-administration/settings/data-model/custom-fields","04.workspace-administration/03.settings/02.data-model/04.custom-fields",{"title":730,"path":731,"stem":732},"Priority Levels","/workspace-administration/settings/data-model/priority-levels","04.workspace-administration/03.settings/02.data-model/05.priority-levels",{"title":734,"path":735,"stem":736},"Effort Points and Time Estimation","/workspace-administration/settings/data-model/effort-points-and-time-estimation","04.workspace-administration/03.settings/02.data-model/06.effort-points-and-time-estimation",{"title":738,"path":739,"stem":740},"Statuses and WIP Limiters","/workspace-administration/settings/data-model/statuses-and-wip-limiters","04.workspace-administration/03.settings/02.data-model/07.statuses-and-wip-limiters",{"title":742,"path":743,"stem":744},"Customizing the Item Drawer","/workspace-administration/settings/data-model/customizing-the-item-drawer","04.workspace-administration/03.settings/02.data-model/08.customizing-the-item-drawer",{"title":746,"path":747,"stem":748,"children":749,"page":84},"Forms","/workspace-administration/settings/forms","04.workspace-administration/03.settings/03.forms",[750],{"title":751,"path":752,"stem":753},"Item Request Forms","/workspace-administration/settings/forms/item-request-forms","04.workspace-administration/03.settings/03.forms/item-request-forms",{"title":755,"path":756,"stem":757,"children":758,"page":84},"Integrations","/workspace-administration/settings/integrations","04.workspace-administration/03.settings/04.integrations",[759,763,767,771,775,778,782,786,790,794],{"title":760,"path":761,"stem":762},"GitHub","/workspace-administration/settings/integrations/github","04.workspace-administration/03.settings/04.integrations/01.github",{"title":764,"path":765,"stem":766},"Connect AI & the MCP Server","/workspace-administration/settings/integrations/connect-ai-and-mcp","04.workspace-administration/03.settings/04.integrations/02.connect-ai-and-mcp",{"title":768,"path":769,"stem":770},"Database Access for BI Tools","/workspace-administration/settings/integrations/database-access","04.workspace-administration/03.settings/04.integrations/03.database-access",{"title":772,"path":773,"stem":774},"Jira Integration","/workspace-administration/settings/integrations/jira","04.workspace-administration/03.settings/04.integrations/04.jira",{"title":206,"path":776,"stem":777},"/workspace-administration/settings/integrations/api-keys","04.workspace-administration/03.settings/04.integrations/05.api-keys",{"title":779,"path":780,"stem":781},"Enterprise SSO","/workspace-administration/settings/integrations/enterprise-sso","04.workspace-administration/03.settings/04.integrations/06.enterprise-sso",{"title":783,"path":784,"stem":785},"Microsoft Teams","/workspace-administration/settings/integrations/microsoft-teams","04.workspace-administration/03.settings/04.integrations/07.microsoft-teams",{"title":787,"path":788,"stem":789},"Slack","/workspace-administration/settings/integrations/slack","04.workspace-administration/03.settings/04.integrations/08.slack",{"title":791,"path":792,"stem":793},"Connected Apps","/workspace-administration/settings/integrations/connected-apps","04.workspace-administration/03.settings/04.integrations/09.connected-apps",{"title":795,"path":796,"stem":797},"Azure DevOps Integration","/workspace-administration/settings/integrations/azure-devops","04.workspace-administration/03.settings/04.integrations/10.azure-devops",{"title":799,"path":800,"stem":801},"Subscription & Billing","/workspace-administration/settings/subscription-and-billing","04.workspace-administration/03.settings/05.subscription-and-billing",{"title":483,"path":803,"stem":804,"children":805,"page":84},"/workspace-administration/permissions","04.workspace-administration/04.permissions",[806,809,812],{"title":194,"path":807,"stem":808},"/workspace-administration/permissions/understanding-permissions","04.workspace-administration/04.permissions/01.understanding-permissions",{"title":198,"path":810,"stem":811},"/workspace-administration/permissions/roles-and-permissions","04.workspace-administration/04.permissions/02.roles-and-permissions",{"title":202,"path":813,"stem":814},"/workspace-administration/permissions/sharing-and-access","04.workspace-administration/04.permissions/03.sharing-and-access",{"title":816,"order":817,"path":818,"stem":819,"children":820,"page":84},"Filters",10,"/workspace-administration/filters","04.workspace-administration/05.filters",[821,824,827,831,835,839,843,846,849,853,873],{"title":589,"path":822,"stem":823},"/workspace-administration/filters/my-work","04.workspace-administration/05.filters/01.my-work",{"title":595,"path":825,"stem":826},"/workspace-administration/filters/notifications","04.workspace-administration/05.filters/02.notifications",{"title":828,"path":829,"stem":830},"Workstreams","/workspace-administration/filters/workstreams","04.workspace-administration/05.filters/03.workstreams",{"title":832,"path":833,"stem":834},"Cycles","/workspace-administration/filters/cycles","04.workspace-administration/05.filters/04.cycles",{"title":836,"path":837,"stem":838},"Boards","/workspace-administration/filters/boards","04.workspace-administration/05.filters/05.boards",{"title":840,"path":841,"stem":842},"Gantt","/workspace-administration/filters/gantt","04.workspace-administration/05.filters/06.gantt",{"title":52,"path":844,"stem":845},"/workspace-administration/filters/items","04.workspace-administration/05.filters/07.items",{"title":495,"path":847,"stem":848},"/workspace-administration/filters/reports","04.workspace-administration/05.filters/08.reports",{"title":850,"path":851,"stem":852},"Automations","/workspace-administration/filters/automations","04.workspace-administration/05.filters/09.automations",{"title":854,"path":855,"stem":856,"children":857,"page":84},"Sort ","/workspace-administration/filters/sort","04.workspace-administration/05.filters/10.sort",[858,861,864,867,870],{"title":589,"path":859,"stem":860},"/workspace-administration/filters/sort/my-work","04.workspace-administration/05.filters/10.sort/01.my-work",{"title":828,"path":862,"stem":863},"/workspace-administration/filters/sort/workstreams","04.workspace-administration/05.filters/10.sort/02.workstreams",{"title":832,"path":865,"stem":866},"/workspace-administration/filters/sort/cycles","04.workspace-administration/05.filters/10.sort/03.cycles",{"title":836,"path":868,"stem":869},"/workspace-administration/filters/sort/boards","04.workspace-administration/05.filters/10.sort/04.boards",{"title":52,"path":871,"stem":872},"/workspace-administration/filters/sort/items","04.workspace-administration/05.filters/10.sort/05.items",{"title":874,"path":875,"stem":876,"children":877,"page":84},"Options","/workspace-administration/filters/options","04.workspace-administration/05.filters/11.options",[878,881,884,888,891],{"title":589,"path":879,"stem":880},"/workspace-administration/filters/options/my-work","04.workspace-administration/05.filters/11.options/01.my-work",{"title":828,"path":882,"stem":883},"/workspace-administration/filters/options/workstreams","04.workspace-administration/05.filters/11.options/02.workstreams",{"title":885,"path":886,"stem":887},"Cycle","/workspace-administration/filters/options/cycle","04.workspace-administration/05.filters/11.options/03.cycle",{"title":836,"path":889,"stem":890},"/workspace-administration/filters/options/boards","04.workspace-administration/05.filters/11.options/04.boards",{"title":52,"path":892,"stem":893},"/workspace-administration/filters/options/items","04.workspace-administration/05.filters/11.options/05.items",{"title":895,"path":896,"stem":897},"AI Assistant","/workspace-administration/ai-assistant","04.workspace-administration/06.ai-assistant",{"title":899,"path":900,"stem":901,"children":902,"page":84},"Planning Concepts & Use Cases","/planning-concepts-and-use-cases","05.planning-concepts-and-use-cases",[903,959],{"title":904,"order":905,"path":906,"stem":907,"children":908,"page":84},"Understanding Project Planning Tools",9,"/planning-concepts-and-use-cases/project-planning-tools","05.planning-concepts-and-use-cases/01.project-planning-tools",[909,942],{"title":910,"path":911,"stem":912,"children":913,"page":84},"Gantt Charts","/planning-concepts-and-use-cases/project-planning-tools/gantt-charts","05.planning-concepts-and-use-cases/01.project-planning-tools/gantt-charts",[914,918,922,926,930,934,938],{"title":915,"path":916,"stem":917},"Gantt Charts Basics","/planning-concepts-and-use-cases/project-planning-tools/gantt-charts/gantt-charts-basics","05.planning-concepts-and-use-cases/01.project-planning-tools/gantt-charts/01.gantt-charts-basics",{"title":919,"path":920,"stem":921},"Create New Gantt","/planning-concepts-and-use-cases/project-planning-tools/gantt-charts/create-new-gantt","05.planning-concepts-and-use-cases/01.project-planning-tools/gantt-charts/02.create-new-gantt",{"title":923,"path":924,"stem":925},"Add to Gantt","/planning-concepts-and-use-cases/project-planning-tools/gantt-charts/add-to-gantt","05.planning-concepts-and-use-cases/01.project-planning-tools/gantt-charts/03.add-to-gantt",{"title":927,"path":928,"stem":929},"Gantt Charts Toolbar","/planning-concepts-and-use-cases/project-planning-tools/gantt-charts/gantt-charts-toolbar","05.planning-concepts-and-use-cases/01.project-planning-tools/gantt-charts/04.gantt-charts-toolbar",{"title":931,"path":932,"stem":933},"Scenario Planning","/planning-concepts-and-use-cases/project-planning-tools/gantt-charts/scenario-planning","05.planning-concepts-and-use-cases/01.project-planning-tools/gantt-charts/05.scenario-planning",{"title":935,"path":936,"stem":937},"Tips for Effective Gantt Chart Planning","/planning-concepts-and-use-cases/project-planning-tools/gantt-charts/tips-for-effective-gantt-chart-planning","05.planning-concepts-and-use-cases/01.project-planning-tools/gantt-charts/06.tips-for-effective-gantt-chart-planning",{"title":939,"path":940,"stem":941},"Link Mode","/planning-concepts-and-use-cases/project-planning-tools/gantt-charts/link-mode","05.planning-concepts-and-use-cases/01.project-planning-tools/gantt-charts/07.link-mode",{"title":943,"path":944,"stem":945,"children":946,"page":84},"Kanban Boards","/planning-concepts-and-use-cases/project-planning-tools/kanban-boards","05.planning-concepts-and-use-cases/01.project-planning-tools/kanban-boards",[947,951,955],{"title":948,"path":949,"stem":950},"Setting Up Your Kanban Board","/planning-concepts-and-use-cases/project-planning-tools/kanban-boards/setting-up-your-kanban-board","05.planning-concepts-and-use-cases/01.project-planning-tools/kanban-boards/01.setting-up-your-kanban-board",{"title":952,"path":953,"stem":954},"Using Kanban for Workflow Visualization","/planning-concepts-and-use-cases/project-planning-tools/kanban-boards/using-kanban-for-workflow-visualization","05.planning-concepts-and-use-cases/01.project-planning-tools/kanban-boards/02.using-kanban-for-workflow-visualization",{"title":956,"path":957,"stem":958},"Kanban Best Practices","/planning-concepts-and-use-cases/project-planning-tools/kanban-boards/kanban-best-practices","05.planning-concepts-and-use-cases/01.project-planning-tools/kanban-boards/03.kanban-best-practices",{"title":960,"order":961,"path":962,"stem":963,"children":964,"page":84},"Use Cases",12,"/planning-concepts-and-use-cases/use-cases","05.planning-concepts-and-use-cases/02.use-cases",[965,968,972,976,980,984],{"title":910,"path":966,"stem":967},"/planning-concepts-and-use-cases/use-cases/gantt-charts","05.planning-concepts-and-use-cases/02.use-cases/01.gantt-charts",{"title":969,"path":970,"stem":971},"Plan a Sprint","/planning-concepts-and-use-cases/use-cases/plan-a-sprint","05.planning-concepts-and-use-cases/02.use-cases/02.plan-a-sprint",{"title":973,"path":974,"stem":975},"Conduct a Standup","/planning-concepts-and-use-cases/use-cases/conduct-a-standup","05.planning-concepts-and-use-cases/02.use-cases/03.conduct-a-standup",{"title":977,"path":978,"stem":979},"Manage Cadences","/planning-concepts-and-use-cases/use-cases/manage-cadences","05.planning-concepts-and-use-cases/02.use-cases/04.manage-cadences",{"title":981,"path":982,"stem":983},"Customizing Item Types","/planning-concepts-and-use-cases/use-cases/customizing-item-types","05.planning-concepts-and-use-cases/02.use-cases/05.customizing-item-types",{"title":985,"path":986,"stem":987},"Backlog Refinement","/planning-concepts-and-use-cases/use-cases/backlog-refinement","05.planning-concepts-and-use-cases/02.use-cases/06.backlog-refinement",{"title":989,"path":990,"stem":991,"children":992,"page":84},"Integrations & Extensibility","/integrations-and-extensibility","06.integrations-and-extensibility",[993,1018,1076],{"title":994,"path":995,"stem":996,"children":997,"page":84},"DevStride MCP Server","/integrations-and-extensibility/devstride-mcp-server","06.integrations-and-extensibility/01.devstride-mcp-server",[998,1002,1006,1010,1014],{"title":999,"path":1000,"stem":1001},"What Is the DevStride MCP Server?","/integrations-and-extensibility/devstride-mcp-server/what-is-the-devstride-mcp-server","06.integrations-and-extensibility/01.devstride-mcp-server/01.what-is-the-devstride-mcp-server",{"title":1003,"path":1004,"stem":1005},"Setting Up Your Connection","/integrations-and-extensibility/devstride-mcp-server/setting-up-your-connection","06.integrations-and-extensibility/01.devstride-mcp-server/02.setting-up-your-connection",{"title":1007,"path":1008,"stem":1009},"Doing Real Work with the DevStride MCP Server","/integrations-and-extensibility/devstride-mcp-server/doing-real-work-with-the-devstride-mcp-server","06.integrations-and-extensibility/01.devstride-mcp-server/03.doing-real-work-with-the-devstride-mcp-server",{"title":1011,"path":1012,"stem":1013},"Best Practices & Tips","/integrations-and-extensibility/devstride-mcp-server/best-practices-and-tips","06.integrations-and-extensibility/01.devstride-mcp-server/04.best-practices-and-tips",{"title":1015,"path":1016,"stem":1017},"Tool Reference","/integrations-and-extensibility/devstride-mcp-server/tool-reference","06.integrations-and-extensibility/01.devstride-mcp-server/05.tool-reference",{"title":1019,"path":1020,"stem":1021,"children":1022},"Plugins","/integrations-and-extensibility/plugins","06.integrations-and-extensibility/02.plugins/index",[1023,1025],{"title":1024,"path":1020,"stem":1021},"Getting Started With Plugins",{"title":1026,"path":1027,"stem":1028,"children":1029,"page":84},"Weekly Logs","/integrations-and-extensibility/plugins/weekly-logs","06.integrations-and-extensibility/02.plugins/01.weekly-logs",[1030,1033,1037,1041,1045,1049,1053,1057,1060,1064,1068,1072],{"title":447,"path":1031,"stem":1032},"/integrations-and-extensibility/plugins/weekly-logs/overview","06.integrations-and-extensibility/02.plugins/01.weekly-logs/01.overview",{"title":1034,"path":1035,"stem":1036},"Grid","/integrations-and-extensibility/plugins/weekly-logs/grid","06.integrations-and-extensibility/02.plugins/01.weekly-logs/02.grid",{"title":1038,"path":1039,"stem":1040},"Views","/integrations-and-extensibility/plugins/weekly-logs/views","06.integrations-and-extensibility/02.plugins/01.weekly-logs/03.views",{"title":1042,"path":1043,"stem":1044},"Navigation","/integrations-and-extensibility/plugins/weekly-logs/navigation","06.integrations-and-extensibility/02.plugins/01.weekly-logs/04.navigation",{"title":1046,"path":1047,"stem":1048},"Proposed Items","/integrations-and-extensibility/plugins/weekly-logs/proposed-items","06.integrations-and-extensibility/02.plugins/01.weekly-logs/05.proposed-items",{"title":1050,"path":1051,"stem":1052},"Search","/integrations-and-extensibility/plugins/weekly-logs/search","06.integrations-and-extensibility/02.plugins/01.weekly-logs/06.search",{"title":1054,"path":1055,"stem":1056},"Time Logging","/integrations-and-extensibility/plugins/weekly-logs/logging","06.integrations-and-extensibility/02.plugins/01.weekly-logs/07.logging",{"title":816,"path":1058,"stem":1059},"/integrations-and-extensibility/plugins/weekly-logs/filtering","06.integrations-and-extensibility/02.plugins/01.weekly-logs/08.filtering",{"title":1061,"path":1062,"stem":1063},"Team Logs","/integrations-and-extensibility/plugins/weekly-logs/team-logs","06.integrations-and-extensibility/02.plugins/01.weekly-logs/09.team-logs",{"title":1065,"path":1066,"stem":1067},"Organization Switch","/integrations-and-extensibility/plugins/weekly-logs/organization-switch","06.integrations-and-extensibility/02.plugins/01.weekly-logs/10.organization-switch",{"title":1069,"path":1070,"stem":1071},"Export","/integrations-and-extensibility/plugins/weekly-logs/export","06.integrations-and-extensibility/02.plugins/01.weekly-logs/11.export",{"title":1073,"path":1074,"stem":1075},"Meetings","/integrations-and-extensibility/plugins/weekly-logs/meetings","06.integrations-and-extensibility/02.plugins/01.weekly-logs/12.meetings",{"title":1077,"path":1078,"stem":1079,"children":1080},"Webhooks","/integrations-and-extensibility/webhooks","06.integrations-and-extensibility/03.webhooks/index",[1081,1082,1086,1090,1094,1098,1102],{"title":1077,"path":1078,"stem":1079},{"title":1083,"path":1084,"stem":1085},"Quickstart","/integrations-and-extensibility/webhooks/quickstart","06.integrations-and-extensibility/03.webhooks/01.quickstart",{"title":1087,"path":1088,"stem":1089},"Events & Payloads","/integrations-and-extensibility/webhooks/events-and-payloads","06.integrations-and-extensibility/03.webhooks/02.events-and-payloads",{"title":1091,"path":1092,"stem":1093},"Verifying Signatures","/integrations-and-extensibility/webhooks/verifying-signatures","06.integrations-and-extensibility/03.webhooks/03.verifying-signatures",{"title":1095,"path":1096,"stem":1097},"Delivery, Retries & Ordering","/integrations-and-extensibility/webhooks/delivery-retries-and-ordering","06.integrations-and-extensibility/03.webhooks/04.delivery-retries-and-ordering",{"title":1099,"path":1100,"stem":1101},"Managing Endpoints","/integrations-and-extensibility/webhooks/managing-endpoints","06.integrations-and-extensibility/03.webhooks/05.managing-endpoints",{"title":1103,"path":1104,"stem":1105},"Troubleshooting & Best Practices","/integrations-and-extensibility/webhooks/troubleshooting-and-best-practices","06.integrations-and-extensibility/03.webhooks/06.troubleshooting-and-best-practices",{"title":1107,"path":1108,"stem":1109,"children":1110,"page":84},"Developer Experience","/developer-experience","07.developer-experience",[1111,1147,1203],{"title":1112,"path":1113,"stem":1114,"children":1115,"page":84},"Developer Guide","/developer-experience/guide","07.developer-experience/00.guide",[1116,1119,1123,1127,1131,1135,1139,1143],{"title":447,"path":1117,"stem":1118},"/developer-experience/guide/overview","07.developer-experience/00.guide/01.overview",{"title":1120,"path":1121,"stem":1122},"Set up your machine","/developer-experience/guide/set-up-your-machine","07.developer-experience/00.guide/02.set-up-your-machine",{"title":1124,"path":1125,"stem":1126},"Credentials and access","/developer-experience/guide/credentials-and-access","07.developer-experience/00.guide/03.credentials-and-access",{"title":1128,"path":1129,"stem":1130},"Plan work","/developer-experience/guide/plan-work","07.developer-experience/00.guide/04.plan-work",{"title":1132,"path":1133,"stem":1134},"Develop","/developer-experience/guide/develop","07.developer-experience/00.guide/05.develop",{"title":1136,"path":1137,"stem":1138},"Release","/developer-experience/guide/release","07.developer-experience/00.guide/06.release",{"title":1140,"path":1141,"stem":1142},"Troubleshooting","/developer-experience/guide/troubleshooting","07.developer-experience/00.guide/07.troubleshooting",{"title":1144,"path":1145,"stem":1146},"For admins: access, keys and stages","/developer-experience/guide/for-admins","07.developer-experience/00.guide/08.for-admins",{"title":1148,"path":1149,"stem":1150,"children":1151,"page":84},"Intro","/developer-experience/intro","07.developer-experience/01.intro",[1152,1156,1159,1163,1167,1171,1175,1179,1183,1187,1191,1195,1199],{"title":1153,"path":1154,"stem":1155},"How DevStride Is Built, and Why","/developer-experience/intro/how-we-build","07.developer-experience/01.intro/00.how-we-build",{"title":5,"path":1157,"stem":1158},"/developer-experience/intro/getting-started","07.developer-experience/01.intro/01.getting-started",{"title":1160,"path":1161,"stem":1162},"Introduction to the ds CLI","/developer-experience/intro/introduction","07.developer-experience/01.intro/02.introduction",{"title":1164,"path":1165,"stem":1166},"Command Reference","/developer-experience/intro/command-reference","07.developer-experience/01.intro/03.command-reference",{"title":1168,"path":1169,"stem":1170},"Local Development","/developer-experience/intro/local-development","07.developer-experience/01.intro/04.local-development",{"title":1172,"path":1173,"stem":1174},"Dockerized Worktree Development","/developer-experience/intro/dockerized-worktree-development","07.developer-experience/01.intro/05.dockerized-worktree-development",{"title":1176,"path":1177,"stem":1178},"Golden Dataset","/developer-experience/intro/golden-dataset","07.developer-experience/01.intro/06.golden-dataset",{"title":1180,"path":1181,"stem":1182},"Deployment","/developer-experience/intro/deployment","07.developer-experience/01.intro/07.deployment",{"title":1184,"path":1185,"stem":1186},"API Development","/developer-experience/intro/api-development","07.developer-experience/01.intro/08.api-development",{"title":1188,"path":1189,"stem":1190},"Stripe Integration","/developer-experience/intro/stripe-integration","07.developer-experience/01.intro/09.stripe-integration",{"title":1192,"path":1193,"stem":1194},"Maintenance & Codebase Checks","/developer-experience/intro/maintenance-and-codebase-checks","07.developer-experience/01.intro/10.maintenance-and-codebase-checks",{"title":1196,"path":1197,"stem":1198},"AI Development","/developer-experience/intro/ai-development","07.developer-experience/01.intro/11.ai-development",{"title":1200,"path":1201,"stem":1202},"Data Conversion Utilities","/developer-experience/intro/data-conversion-utilities","07.developer-experience/01.intro/12.data-conversion-utilities",{"title":1204,"path":1205,"stem":1206,"children":1207,"page":84},"Agentic Skills","/developer-experience/agentic-skills","07.developer-experience/02.agentic-skills",[1208,1212,1216,1220,1224,1228,1232,1236],{"title":1209,"path":1210,"stem":1211},"The Idea Behind the Loop","/developer-experience/agentic-skills/the-idea-behind-the-loop","07.developer-experience/02.agentic-skills/00.the-idea-behind-the-loop",{"title":1213,"path":1214,"stem":1215},"DevStride for Claude Code","/developer-experience/agentic-skills/claude-code-skills-overview","07.developer-experience/02.agentic-skills/01.claude-code-skills-overview",{"title":1217,"path":1218,"stem":1219},"Getting the Skills","/developer-experience/agentic-skills/getting-the-skills","07.developer-experience/02.agentic-skills/02.getting-the-skills",{"title":1221,"path":1222,"stem":1223},"Guided Setup","/developer-experience/agentic-skills/setup-guide","07.developer-experience/02.agentic-skills/03.setup-guide",{"title":1225,"path":1226,"stem":1227},"Skill Audit","/developer-experience/agentic-skills/skill-audit","07.developer-experience/02.agentic-skills/04.skill-audit",{"title":1229,"path":1230,"stem":1231},"The Planning Loop","/developer-experience/agentic-skills/claude-skills-planning-loop","07.developer-experience/02.agentic-skills/05.claude-skills-planning-loop",{"title":1233,"path":1234,"stem":1235},"The Delivery Loop","/developer-experience/agentic-skills/claude-skills-delivery-loop","07.developer-experience/02.agentic-skills/06.claude-skills-delivery-loop",{"title":1237,"path":1238,"stem":1239},"Configuration Reference","/developer-experience/agentic-skills/configuration-reference","07.developer-experience/02.agentic-skills/07.configuration-reference",{"title":1241,"order":1242,"path":1243,"stem":1244,"children":1245,"page":84},"Release Notes",11,"/release-notes","08.release-notes",[1246,1250,1254,1258,1262,1266,1270,1274,1278,1282,1286,1290,1294,1298,1302,1306,1310,1314,1318,1322,1326,1330,1334,1338,1342,1346,1350,1354,1358,1362,1366,1370,1374,1378,1382,1386,1390,1394,1398,1402,1406,1410,1414,1418,1422,1426,1430,1434,1438,1442,1446,1450,1454,1458,1462,1466,1470,1474,1478,1482,1486,1490,1494,1498,1502,1506,1510,1514,1518,1522,1526,1530,1534,1538,1542,1546,1550,1554,1558,1562,1566,1570,1574],{"title":1247,"path":1248,"stem":1249},"2026-10-12","/release-notes/2026-10-12","08.release-notes/83.2026-10-12",{"title":1251,"path":1252,"stem":1253},"2026-10-03","/release-notes/2026-10-03","08.release-notes/82.2026-10-03",{"title":1255,"path":1256,"stem":1257},"2026-09-17","/release-notes/2026-09-17","08.release-notes/81.2026-09-17",{"title":1259,"path":1260,"stem":1261},"2026-09-13","/release-notes/2026-09-13","08.release-notes/80.2026-09-13",{"title":1263,"path":1264,"stem":1265},"2026-09-11","/release-notes/2026-09-11","08.release-notes/79.2026-09-11",{"title":1267,"path":1268,"stem":1269},"2026-09-01","/release-notes/2026-09-01","08.release-notes/78.2026-09-01",{"title":1271,"path":1272,"stem":1273},"2026-08-29","/release-notes/2026-08-29","08.release-notes/77.2026-08-29",{"title":1275,"path":1276,"stem":1277},"2026-08-27","/release-notes/2026-08-27","08.release-notes/76.2026-08-27",{"title":1279,"path":1280,"stem":1281},"2026-08-25","/release-notes/2026-08-25","08.release-notes/75.2026-08-25",{"title":1283,"path":1284,"stem":1285},"2026-08-22","/release-notes/2026-08-22","08.release-notes/74.2026-08-22",{"title":1287,"path":1288,"stem":1289},"2026-08-21","/release-notes/2026-08-21","08.release-notes/73.2026-08-21",{"title":1291,"path":1292,"stem":1293},"2026-08-17","/release-notes/2026-08-17","08.release-notes/72.2026-08-17",{"title":1295,"path":1296,"stem":1297},"2026-08-16","/release-notes/2026-08-16","08.release-notes/71.2026-08-16",{"title":1299,"path":1300,"stem":1301},"2026-08-14","/release-notes/2026-08-14","08.release-notes/70.2026-08-14",{"title":1303,"path":1304,"stem":1305},"2026-08-11","/release-notes/2026-08-11","08.release-notes/69.2026-08-11",{"title":1307,"path":1308,"stem":1309},"2026-08-07","/release-notes/2026-08-07","08.release-notes/68.2026-08-07",{"title":1311,"path":1312,"stem":1313},"2026-08-05","/release-notes/2026-08-05","08.release-notes/67.2026-08-05",{"title":1315,"path":1316,"stem":1317},"2026-08-01","/release-notes/2026-08-01","08.release-notes/66.2026-08-01",{"title":1319,"path":1320,"stem":1321},"2026-07-30","/release-notes/2026-07-30","08.release-notes/65.2026-07-30",{"title":1323,"path":1324,"stem":1325},"2026-07-28","/release-notes/2026-07-28","08.release-notes/64.2026-07-28",{"title":1327,"path":1328,"stem":1329},"2026-07-22","/release-notes/2026-07-22","08.release-notes/63.2026-07-22",{"title":1331,"path":1332,"stem":1333},"2026-07-19","/release-notes/2026-07-19","08.release-notes/62.2026-07-19",{"title":1335,"path":1336,"stem":1337},"2026-07-17","/release-notes/2026-07-17","08.release-notes/61.2026-07-17",{"title":1339,"path":1340,"stem":1341},"2026-07-15","/release-notes/2026-07-15","08.release-notes/60.2026-07-15",{"title":1343,"path":1344,"stem":1345},"2026-07-12","/release-notes/2026-07-12","08.release-notes/59.2026-07-12",{"title":1347,"path":1348,"stem":1349},"2026-07-11","/release-notes/2026-07-11","08.release-notes/58.2026-07-11",{"title":1351,"path":1352,"stem":1353},"2026-07-09","/release-notes/2026-07-09","08.release-notes/57.2026-07-09",{"title":1355,"path":1356,"stem":1357},"2026-07-07","/release-notes/2026-07-07","08.release-notes/56.2026-07-07",{"title":1359,"path":1360,"stem":1361},"2026-07-06","/release-notes/2026-07-06","08.release-notes/55.2026-07-06",{"title":1363,"path":1364,"stem":1365},"2026-07-02","/release-notes/2026-07-02","08.release-notes/54.2026-07-02",{"title":1367,"path":1368,"stem":1369},"2026-06-30","/release-notes/2026-06-30","08.release-notes/53.2026-06-30",{"title":1371,"path":1372,"stem":1373},"2026-06-17","/release-notes/2026-06-17","08.release-notes/52.2026-06-17",{"title":1375,"path":1376,"stem":1377},"2026-06-12","/release-notes/2026-06-12","08.release-notes/51.2026-06-12",{"title":1379,"path":1380,"stem":1381},"2026-05-29","/release-notes/2026-05-29","08.release-notes/50.2026-05-29",{"title":1383,"path":1384,"stem":1385},"2026-05-13","/release-notes/2026-05-13","08.release-notes/49.2026-05-13",{"title":1387,"path":1388,"stem":1389},"2026-04-29","/release-notes/2026-04-29","08.release-notes/48.2026-04-29",{"title":1391,"path":1392,"stem":1393},"2026-04-14","/release-notes/2026-04-14","08.release-notes/47.2026-04-14",{"title":1395,"path":1396,"stem":1397},"2026-04-09","/release-notes/2026-04-09","08.release-notes/46.2026-04-09",{"title":1399,"path":1400,"stem":1401},"2026-03-13","/release-notes/2026-03-13","08.release-notes/45.2026-03-13",{"title":1403,"path":1404,"stem":1405},"2026-02-16","/release-notes/2026-02-16","08.release-notes/44.2026-02-16",{"title":1407,"path":1408,"stem":1409},"2026-02-06","/release-notes/2026-02-06","08.release-notes/43.2026-02-06",{"title":1411,"path":1412,"stem":1413},"2026-01-30","/release-notes/2026-01-30","08.release-notes/42.2026-01-30",{"title":1415,"path":1416,"stem":1417},"2026-01-19","/release-notes/2026-01-19","08.release-notes/41.2026-01-19",{"title":1419,"path":1420,"stem":1421},"2025-09-27","/release-notes/2025-09-27","08.release-notes/40.2025-09-27",{"title":1423,"path":1424,"stem":1425},"2025-07-07","/release-notes/2025-07-07","08.release-notes/39.2025-07-07",{"title":1427,"path":1428,"stem":1429},"2025-06-16","/release-notes/2025-06-16","08.release-notes/38.2025-06-16",{"title":1431,"path":1432,"stem":1433},"2025-05-29","/release-notes/2025-05-29","08.release-notes/37.2025-05-29",{"title":1435,"path":1436,"stem":1437},"2025-04-28","/release-notes/2025-04-28","08.release-notes/36.2025-04-28",{"title":1439,"path":1440,"stem":1441},"2025-03-24","/release-notes/2025-03-24","08.release-notes/35.2025-03-24",{"title":1443,"path":1444,"stem":1445},"2024-12-20","/release-notes/2024-12-20","08.release-notes/34.2024-12-20",{"title":1447,"path":1448,"stem":1449},"2024-10-21","/release-notes/2024-10-21","08.release-notes/33.2024-10-21",{"title":1451,"path":1452,"stem":1453},"2024-10-11","/release-notes/2024-10-11","08.release-notes/32.2024-10-11",{"title":1455,"path":1456,"stem":1457},"2024-09-11","/release-notes/2024-09-11","08.release-notes/31.2024-09-11",{"title":1459,"path":1460,"stem":1461},"2024-08-28","/release-notes/2024-08-28","08.release-notes/30.2024-08-28",{"title":1463,"path":1464,"stem":1465},"2024-07-10","/release-notes/2024-07-10","08.release-notes/29.2024-07-10",{"title":1467,"path":1468,"stem":1469},"2024-06-24","/release-notes/2024-06-24","08.release-notes/28.2024-06-24",{"title":1471,"path":1472,"stem":1473},"2024-05-15","/release-notes/2024-05-15","08.release-notes/27.2024-05-15",{"title":1475,"path":1476,"stem":1477},"2024-04-29","/release-notes/2024-04-29","08.release-notes/26.2024-04-29",{"title":1479,"path":1480,"stem":1481},"2024-03-18","/release-notes/2024-03-18","08.release-notes/25.2024-03-18",{"title":1483,"path":1484,"stem":1485},"2024-03-04","/release-notes/2024-03-04","08.release-notes/24.2024-03-04",{"title":1487,"path":1488,"stem":1489},"2024-02-26","/release-notes/2024-02-26","08.release-notes/23.2024-02-26",{"title":1491,"path":1492,"stem":1493},"2024-01-17","/release-notes/2024-01-17","08.release-notes/22.2024-01-17",{"title":1495,"path":1496,"stem":1497},"2023-12-27","/release-notes/2023-12-27","08.release-notes/21.2023-12-27",{"title":1499,"path":1500,"stem":1501},"2023-11-08","/release-notes/2023-11-08","08.release-notes/20.2023-11-08",{"title":1503,"path":1504,"stem":1505},"2023-10-10","/release-notes/2023-10-10","08.release-notes/19.2023-10-10",{"title":1507,"path":1508,"stem":1509},"2023-09-27","/release-notes/2023-09-27","08.release-notes/18.2023-09-27",{"title":1511,"path":1512,"stem":1513},"2023-09-18","/release-notes/2023-09-18","08.release-notes/17.2023-09-18",{"title":1515,"path":1516,"stem":1517},"2023-09-04","/release-notes/2023-09-04","08.release-notes/16.2023-09-04",{"title":1519,"path":1520,"stem":1521},"2023-08-04","/release-notes/2023-08-04","08.release-notes/15.2023-08-04",{"title":1523,"path":1524,"stem":1525},"2023-07-11","/release-notes/2023-07-11","08.release-notes/14.2023-07-11",{"title":1527,"path":1528,"stem":1529},"2023-06-21","/release-notes/2023-06-21","08.release-notes/13.2023-06-21",{"title":1531,"path":1532,"stem":1533},"2023-05-24","/release-notes/2023-05-24","08.release-notes/12.2023-05-24",{"title":1535,"path":1536,"stem":1537},"2023-04-24","/release-notes/2023-04-24","08.release-notes/11.2023-04-24",{"title":1539,"path":1540,"stem":1541},"2023-04-20","/release-notes/2023-04-20","08.release-notes/10.2023-04-20",{"title":1543,"path":1544,"stem":1545},"2023-03-20","/release-notes/2023-03-20","08.release-notes/09.2023-03-20",{"title":1547,"path":1548,"stem":1549},"2023-02-28","/release-notes/2023-02-28","08.release-notes/08.2023-02-28",{"title":1551,"path":1552,"stem":1553},"2023-01-17","/release-notes/2023-01-17","08.release-notes/07.2023-01-17",{"title":1555,"path":1556,"stem":1557},"2023-01-23","/release-notes/2023-01-23","08.release-notes/06.2023-01-23",{"title":1559,"path":1560,"stem":1561},"2022-12-21","/release-notes/2022-12-21","08.release-notes/05.2022-12-21",{"title":1563,"path":1564,"stem":1565},"2022-12-06","/release-notes/2022-12-06","08.release-notes/04.2022-12-06",{"title":1567,"path":1568,"stem":1569},"2022-11-15","/release-notes/2022-11-15","08.release-notes/03.2022-11-15",{"title":1571,"path":1572,"stem":1573},"2022-11-02","/release-notes/2022-11-02","08.release-notes/02.2022-11-02",{"title":1575,"path":1576,"stem":1577},"2022-10-17","/release-notes/2022-10-17","08.release-notes/01.2022-10-17",{"title":1579,"path":1580,"target":1581},"API","https://api-docs.devstride.com/","_blank",{"id":1583,"title":1091,"body":1584,"description":4481,"extension":4482,"links":4483,"meta":4484,"navigation":1925,"path":1092,"seo":4485,"stem":1093,"__hash__":4486},"docs/06.integrations-and-extensibility/03.webhooks/03.verifying-signatures.md",{"type":1585,"value":1586,"toc":4465},"minimark",[1587,1595,1600,1612,1657,1671,1675,1678,1822,1839,1842,1852,1870,1873,2611,2624,2628,2636,2641,2644,2818,2836,2840,3130,3140,3179,3183,3351,3370,3374,3570,3588,3592,3595,3813,3816,3870,3874,3877,3906,3910,3937,3941,3959,3965,3971,3975,3982,4123,4130,4149,4153,4156,4212,4411,4461],[1588,1589,1590,1591],"p",{},"Your endpoint's URL is public, so anyone can send it a request. The signature is how you know a request really came from DevStride and was not changed on the way. ",[1592,1593,1594],"strong",{},"Verify it on every request, before you trust or act on anything in it.",[1596,1597,1599],"h2",{"id":1598},"how-signing-works","How signing works",[1588,1601,1602,1603,1606,1607,1611],{},"Every endpoint has its own ",[1592,1604,1605],{},"signing secret",", a value that starts with ",[1608,1609,1610],"code",{},"whsec_",", shown once when you create the endpoint or rotate its secret. For each delivery attempt DevStride:",[1613,1614,1615,1623,1636,1646],"ol",{},[1616,1617,1618,1619,1622],"li",{},"takes the current time in Unix seconds, ",[1608,1620,1621],{},"t",";",[1616,1624,1625,1626,1629,1630,1632,1633,1622],{},"builds the ",[1592,1627,1628],{},"signed string",": ",[1608,1631,1621],{},", a full stop, then the exact bytes of the request body: ",[1608,1634,1635],{},"{t}.{body}",[1616,1637,1638,1639,1642,1643,1645],{},"computes ",[1592,1640,1641],{},"HMAC-SHA256"," of that string, keyed with the whole secret (including the ",[1608,1644,1610],{}," prefix, as UTF-8 bytes), and writes it as 64 lowercase hex characters;",[1616,1647,1648,1649,1652,1653,1656],{},"sends it in the ",[1608,1650,1651],{},"X-DevStride-Signature"," header as ",[1608,1654,1655],{},"t=\u003Ct>,v1=\u003Chex>",".",[1588,1658,1659,1660,1663,1664,1667,1668,1670],{},"To verify, your receiver repeats step 3 with its copy of the secret and compares the result with the ",[1608,1661,1662],{},"v1"," value in ",[1592,1665,1666],{},"constant time",", after checking that ",[1608,1669,1621],{}," is recent.",[1596,1672,1674],{"id":1673},"headers-and-signature","Headers and signature",[1588,1676,1677],{},"Every delivery carries these headers (header names are case-insensitive):",[1679,1680,1681,1697],"table",{},[1682,1683,1684],"thead",{},[1685,1686,1687,1691,1694],"tr",{},[1688,1689,1690],"th",{},"Header",[1688,1692,1693],{},"Example",[1688,1695,1696],{},"Meaning",[1698,1699,1700,1715,1733,1752,1773,1792,1807],"tbody",{},[1685,1701,1702,1707,1712],{},[1703,1704,1705],"td",{},[1608,1706,1651],{},[1703,1708,1709],{},[1608,1710,1711],{},"t=1760709791,v1=a8bb78…",[1703,1713,1714],{},"The timestamp and one or two signatures.",[1685,1716,1717,1722,1727],{},[1703,1718,1719],{},[1608,1720,1721],{},"X-DevStride-Timestamp",[1703,1723,1724],{},[1608,1725,1726],{},"1760709791",[1703,1728,1729,1730,1732],{},"The same ",[1608,1731,1621],{},", on its own.",[1685,1734,1735,1740,1745],{},[1703,1736,1737],{},[1608,1738,1739],{},"X-DevStride-Event-Id",[1703,1741,1742],{},[1608,1743,1744],{},"2f6c1d0a-8b4e-5c3f-9a71-0e5d4c3b2a19",[1703,1746,1747,1748,1751],{},"The event's ",[1608,1749,1750],{},"id",", identical on every retry and replay. Deduplicate on it.",[1685,1753,1754,1759,1764],{},[1703,1755,1756],{},[1608,1757,1758],{},"X-DevStride-Delivery",[1703,1760,1761],{},[1608,1762,1763],{},"9f1c6c3a-0b7e-4e2d-8d0a-4c2f3b1a5e77",[1703,1765,1766,1767,1772],{},"This delivery's id in the ",[1768,1769,1771],"a",{"href":1770},"/integrations-and-extensibility/webhooks/managing-endpoints#the-delivery-log","delivery log",": one per event per endpoint, the same across retries and replays.",[1685,1774,1775,1780,1785],{},[1703,1776,1777],{},[1608,1778,1779],{},"X-DevStride-Event-Type",[1703,1781,1782],{},[1608,1783,1784],{},"comment.created",[1703,1786,1787,1788,1791],{},"The event ",[1608,1789,1790],{},"type",", so you can route before parsing.",[1685,1793,1794,1799,1804],{},[1703,1795,1796],{},[1608,1797,1798],{},"Content-Type",[1703,1800,1801],{},[1608,1802,1803],{},"application/json",[1703,1805,1806],{},"Always JSON.",[1685,1808,1809,1814,1819],{},[1703,1810,1811],{},[1608,1812,1813],{},"User-Agent",[1703,1815,1816],{},[1608,1817,1818],{},"DevStride-Webhooks/1",[1703,1820,1821],{},"Identifies DevStride. Useful for routing; never proof of origin.",[1588,1823,1824,1825,1828,1829,1832,1833,1835,1836,1838],{},"A ",[1608,1826,1827],{},"ping"," from the ",[1592,1830,1831],{},"Test"," button carries the first three, but not ",[1608,1834,1758],{}," or ",[1608,1837,1779],{},", because a test is not part of the delivery log.",[1588,1840,1841],{},"The header grammar is:",[1843,1844,1850],"pre",{"className":1845,"code":1847,"language":1848,"meta":1849},[1846],"language-text","X-DevStride-Signature: t=\u003Cunix seconds>,v1=\u003C64 hex chars>[,v1=\u003C64 hex chars>]\n","text","",[1608,1851,1847],{"__ignoreMap":1849},[1588,1853,1854,1855,1858,1859,1861,1862,1866,1867,1869],{},"Accept the request when ",[1592,1856,1857],{},"any"," ",[1608,1860,1662],{}," value matches. For 24 hours after a ",[1768,1863,1865],{"href":1864},"#secret-rotation","secret rotation"," there are two ",[1608,1868,1662],{}," values, one per secret, so either your old or your new secret verifies.",[1588,1871,1872],{},"Here is the reference implementation in TypeScript (Node.js). DevStride's own test suite runs this exact function against the signer the delivery worker uses, so it is known to be correct:",[1843,1874,1878],{"className":1875,"code":1876,"language":1877,"meta":1849,"style":1849},"language-ts shiki shiki-themes material-theme-lighter material-theme material-theme-palenight","import { createHmac, timingSafeEqual } from 'node:crypto';\n\nfunction verifyDevStrideSignature(\n  signatureHeader: string | undefined,\n  rawBody: Buffer,\n  secret: string,\n  nowSeconds = Math.floor(Date.now() / 1000)\n): boolean {\n  if (!signatureHeader) return false;\n  let timestamp: number | undefined;\n  const signatures: Buffer[] = [];\n  for (const part of signatureHeader.split(',')) {\n    const [key, value] = part.trim().split('=');\n    if (key === 't' && value && /^\\d+$/.test(value)) timestamp = Number(value);\n    else if (key === 'v1' && value && /^[0-9a-f]{64}$/.test(value))\n      signatures.push(Buffer.from(value, 'hex'));\n  }\n  if (timestamp === undefined || signatures.length === 0) return false;\n  // Older (or newer) than 5 minutes: a replayed capture, or a clock to fix.\n  if (Math.abs(nowSeconds - timestamp) > 300) return false;\n  const expected = createHmac('sha256', secret)\n    .update(`${timestamp}.`)\n    .update(rawBody)\n    .digest();\n  // Two v1 values appear for 24 h after a rotation; either secret verifies.\n  return signatures.some(\n    (candidate) =>\n      candidate.length === expected.length &&\n      timingSafeEqual(candidate, expected)\n  );\n}\n","ts",[1608,1879,1880,1921,1927,1940,1962,1974,1985,2022,2033,2060,2079,2102,2141,2190,2261,2316,2355,2361,2397,2404,2445,2473,2499,2513,2525,2531,2546,2560,2581,2597,2605],{"__ignoreMap":1849},[1881,1882,1884,1888,1892,1896,1899,1902,1905,1908,1911,1915,1918],"span",{"class":1883,"line":17},"line",[1881,1885,1887],{"class":1886},"s7zQu","import",[1881,1889,1891],{"class":1890},"sMK4o"," {",[1881,1893,1895],{"class":1894},"sTEyZ"," createHmac",[1881,1897,1898],{"class":1890},",",[1881,1900,1901],{"class":1894}," timingSafeEqual",[1881,1903,1904],{"class":1890}," }",[1881,1906,1907],{"class":1886}," from",[1881,1909,1910],{"class":1890}," '",[1881,1912,1914],{"class":1913},"sfazB","node:crypto",[1881,1916,1917],{"class":1890},"'",[1881,1919,1920],{"class":1890},";\n",[1881,1922,1923],{"class":1883,"line":244},[1881,1924,1926],{"emptyLinePlaceholder":1925},true,"\n",[1881,1928,1929,1933,1937],{"class":1883,"line":302},[1881,1930,1932],{"class":1931},"spNyl","function",[1881,1934,1936],{"class":1935},"s2Zo4"," verifyDevStrideSignature",[1881,1938,1939],{"class":1890},"(\n",[1881,1941,1942,1946,1949,1953,1956,1959],{"class":1883,"line":377},[1881,1943,1945],{"class":1944},"sHdIc","  signatureHeader",[1881,1947,1948],{"class":1890},":",[1881,1950,1952],{"class":1951},"sBMFI"," string",[1881,1954,1955],{"class":1890}," |",[1881,1957,1958],{"class":1951}," undefined",[1881,1960,1961],{"class":1890},",\n",[1881,1963,1964,1967,1969,1972],{"class":1883,"line":649},[1881,1965,1966],{"class":1944},"  rawBody",[1881,1968,1948],{"class":1890},[1881,1970,1971],{"class":1951}," Buffer",[1881,1973,1961],{"class":1890},[1881,1975,1976,1979,1981,1983],{"class":1883,"line":659},[1881,1977,1978],{"class":1944},"  secret",[1881,1980,1948],{"class":1890},[1881,1982,1952],{"class":1951},[1881,1984,1961],{"class":1890},[1881,1986,1987,1990,1993,1996,1998,2001,2004,2006,2009,2012,2015,2019],{"class":1883,"line":596},[1881,1988,1989],{"class":1944},"  nowSeconds",[1881,1991,1992],{"class":1890}," =",[1881,1994,1995],{"class":1894}," Math",[1881,1997,1656],{"class":1890},[1881,1999,2000],{"class":1935},"floor",[1881,2002,2003],{"class":1894},"(Date",[1881,2005,1656],{"class":1890},[1881,2007,2008],{"class":1935},"now",[1881,2010,2011],{"class":1894},"() ",[1881,2013,2014],{"class":1890},"/",[1881,2016,2018],{"class":2017},"sbssI"," 1000",[1881,2020,2021],{"class":1894},")\n",[1881,2023,2024,2027,2030],{"class":1883,"line":280},[1881,2025,2026],{"class":1890},"):",[1881,2028,2029],{"class":1951}," boolean",[1881,2031,2032],{"class":1890}," {\n",[1881,2034,2035,2038,2042,2045,2048,2051,2054,2058],{"class":1883,"line":905},[1881,2036,2037],{"class":1886},"  if",[1881,2039,2041],{"class":2040},"swJcz"," (",[1881,2043,2044],{"class":1890},"!",[1881,2046,2047],{"class":1894},"signatureHeader",[1881,2049,2050],{"class":2040},") ",[1881,2052,2053],{"class":1886},"return",[1881,2055,2057],{"class":2056},"sfNiH"," false",[1881,2059,1920],{"class":1890},[1881,2061,2062,2065,2068,2070,2073,2075,2077],{"class":1883,"line":817},[1881,2063,2064],{"class":1931},"  let",[1881,2066,2067],{"class":1894}," timestamp",[1881,2069,1948],{"class":1890},[1881,2071,2072],{"class":1951}," number",[1881,2074,1955],{"class":1890},[1881,2076,1958],{"class":1951},[1881,2078,1920],{"class":1890},[1881,2080,2081,2084,2087,2089,2091,2094,2097,2100],{"class":1883,"line":1242},[1881,2082,2083],{"class":1931},"  const",[1881,2085,2086],{"class":1894}," signatures",[1881,2088,1948],{"class":1890},[1881,2090,1971],{"class":1951},[1881,2092,2093],{"class":2040},"[] ",[1881,2095,2096],{"class":1890},"=",[1881,2098,2099],{"class":2040}," []",[1881,2101,1920],{"class":1890},[1881,2103,2104,2107,2109,2112,2115,2118,2121,2123,2126,2129,2131,2133,2135,2138],{"class":1883,"line":961},[1881,2105,2106],{"class":1886},"  for",[1881,2108,2041],{"class":2040},[1881,2110,2111],{"class":1931},"const",[1881,2113,2114],{"class":1894}," part",[1881,2116,2117],{"class":1890}," of",[1881,2119,2120],{"class":1894}," signatureHeader",[1881,2122,1656],{"class":1890},[1881,2124,2125],{"class":1935},"split",[1881,2127,2128],{"class":2040},"(",[1881,2130,1917],{"class":1890},[1881,2132,1898],{"class":1913},[1881,2134,1917],{"class":1890},[1881,2136,2137],{"class":2040},")) ",[1881,2139,2140],{"class":1890},"{\n",[1881,2142,2144,2147,2150,2153,2155,2158,2161,2163,2165,2167,2170,2173,2175,2177,2179,2181,2183,2185,2188],{"class":1883,"line":2143},13,[1881,2145,2146],{"class":1931},"    const",[1881,2148,2149],{"class":1890}," [",[1881,2151,2152],{"class":1894},"key",[1881,2154,1898],{"class":1890},[1881,2156,2157],{"class":1894}," value",[1881,2159,2160],{"class":1890},"]",[1881,2162,1992],{"class":1890},[1881,2164,2114],{"class":1894},[1881,2166,1656],{"class":1890},[1881,2168,2169],{"class":1935},"trim",[1881,2171,2172],{"class":2040},"()",[1881,2174,1656],{"class":1890},[1881,2176,2125],{"class":1935},[1881,2178,2128],{"class":2040},[1881,2180,1917],{"class":1890},[1881,2182,2096],{"class":1913},[1881,2184,1917],{"class":1890},[1881,2186,2187],{"class":2040},")",[1881,2189,1920],{"class":1890},[1881,2191,2193,2196,2198,2200,2203,2205,2207,2209,2212,2214,2216,2219,2222,2225,2228,2231,2233,2235,2238,2240,2243,2245,2248,2250,2253,2255,2257,2259],{"class":1883,"line":2192},14,[1881,2194,2195],{"class":1886},"    if",[1881,2197,2041],{"class":2040},[1881,2199,2152],{"class":1894},[1881,2201,2202],{"class":1890}," ===",[1881,2204,1910],{"class":1890},[1881,2206,1621],{"class":1913},[1881,2208,1917],{"class":1890},[1881,2210,2211],{"class":1890}," &&",[1881,2213,2157],{"class":1894},[1881,2215,2211],{"class":1890},[1881,2217,2218],{"class":1890}," /",[1881,2220,2221],{"class":1886},"^",[1881,2223,2224],{"class":1913},"\\d",[1881,2226,2227],{"class":1890},"+",[1881,2229,2230],{"class":1886},"$",[1881,2232,2014],{"class":1890},[1881,2234,1656],{"class":1890},[1881,2236,2237],{"class":1935},"test",[1881,2239,2128],{"class":2040},[1881,2241,2242],{"class":1894},"value",[1881,2244,2137],{"class":2040},[1881,2246,2247],{"class":1894},"timestamp",[1881,2249,1992],{"class":1890},[1881,2251,2252],{"class":1935}," Number",[1881,2254,2128],{"class":2040},[1881,2256,2242],{"class":1894},[1881,2258,2187],{"class":2040},[1881,2260,1920],{"class":1890},[1881,2262,2264,2267,2270,2272,2274,2276,2278,2280,2282,2284,2286,2288,2290,2292,2295,2298,2301,2303,2305,2307,2309,2311,2313],{"class":1883,"line":2263},15,[1881,2265,2266],{"class":1886},"    else",[1881,2268,2269],{"class":1886}," if",[1881,2271,2041],{"class":2040},[1881,2273,2152],{"class":1894},[1881,2275,2202],{"class":1890},[1881,2277,1910],{"class":1890},[1881,2279,1662],{"class":1913},[1881,2281,1917],{"class":1890},[1881,2283,2211],{"class":1890},[1881,2285,2157],{"class":1894},[1881,2287,2211],{"class":1890},[1881,2289,2218],{"class":1890},[1881,2291,2221],{"class":1886},[1881,2293,2294],{"class":1890},"[",[1881,2296,2297],{"class":1913},"0-9a-f",[1881,2299,2300],{"class":1890},"]{64}",[1881,2302,2230],{"class":1886},[1881,2304,2014],{"class":1890},[1881,2306,1656],{"class":1890},[1881,2308,2237],{"class":1935},[1881,2310,2128],{"class":2040},[1881,2312,2242],{"class":1894},[1881,2314,2315],{"class":2040},"))\n",[1881,2317,2319,2322,2324,2327,2329,2332,2334,2337,2339,2341,2343,2345,2348,2350,2353],{"class":1883,"line":2318},16,[1881,2320,2321],{"class":1894},"      signatures",[1881,2323,1656],{"class":1890},[1881,2325,2326],{"class":1935},"push",[1881,2328,2128],{"class":2040},[1881,2330,2331],{"class":1894},"Buffer",[1881,2333,1656],{"class":1890},[1881,2335,2336],{"class":1935},"from",[1881,2338,2128],{"class":2040},[1881,2340,2242],{"class":1894},[1881,2342,1898],{"class":1890},[1881,2344,1910],{"class":1890},[1881,2346,2347],{"class":1913},"hex",[1881,2349,1917],{"class":1890},[1881,2351,2352],{"class":2040},"))",[1881,2354,1920],{"class":1890},[1881,2356,2358],{"class":1883,"line":2357},17,[1881,2359,2360],{"class":1890},"  }\n",[1881,2362,2364,2366,2368,2370,2372,2374,2377,2379,2381,2384,2386,2389,2391,2393,2395],{"class":1883,"line":2363},18,[1881,2365,2037],{"class":1886},[1881,2367,2041],{"class":2040},[1881,2369,2247],{"class":1894},[1881,2371,2202],{"class":1890},[1881,2373,1958],{"class":1890},[1881,2375,2376],{"class":1890}," ||",[1881,2378,2086],{"class":1894},[1881,2380,1656],{"class":1890},[1881,2382,2383],{"class":1894},"length",[1881,2385,2202],{"class":1890},[1881,2387,2388],{"class":2017}," 0",[1881,2390,2050],{"class":2040},[1881,2392,2053],{"class":1886},[1881,2394,2057],{"class":2056},[1881,2396,1920],{"class":1890},[1881,2398,2400],{"class":1883,"line":2399},19,[1881,2401,2403],{"class":2402},"sHwdD","  // Older (or newer) than 5 minutes: a replayed capture, or a clock to fix.\n",[1881,2405,2407,2409,2411,2414,2416,2419,2421,2424,2427,2429,2431,2434,2437,2439,2441,2443],{"class":1883,"line":2406},20,[1881,2408,2037],{"class":1886},[1881,2410,2041],{"class":2040},[1881,2412,2413],{"class":1894},"Math",[1881,2415,1656],{"class":1890},[1881,2417,2418],{"class":1935},"abs",[1881,2420,2128],{"class":2040},[1881,2422,2423],{"class":1894},"nowSeconds",[1881,2425,2426],{"class":1890}," -",[1881,2428,2067],{"class":1894},[1881,2430,2050],{"class":2040},[1881,2432,2433],{"class":1890},">",[1881,2435,2436],{"class":2017}," 300",[1881,2438,2050],{"class":2040},[1881,2440,2053],{"class":1886},[1881,2442,2057],{"class":2056},[1881,2444,1920],{"class":1890},[1881,2446,2448,2450,2453,2455,2457,2459,2461,2464,2466,2468,2471],{"class":1883,"line":2447},21,[1881,2449,2083],{"class":1931},[1881,2451,2452],{"class":1894}," expected",[1881,2454,1992],{"class":1890},[1881,2456,1895],{"class":1935},[1881,2458,2128],{"class":2040},[1881,2460,1917],{"class":1890},[1881,2462,2463],{"class":1913},"sha256",[1881,2465,1917],{"class":1890},[1881,2467,1898],{"class":1890},[1881,2469,2470],{"class":1894}," secret",[1881,2472,2021],{"class":2040},[1881,2474,2476,2479,2482,2484,2487,2489,2492,2494,2497],{"class":1883,"line":2475},22,[1881,2477,2478],{"class":1890},"    .",[1881,2480,2481],{"class":1935},"update",[1881,2483,2128],{"class":2040},[1881,2485,2486],{"class":1890},"`${",[1881,2488,2247],{"class":1894},[1881,2490,2491],{"class":1890},"}",[1881,2493,1656],{"class":1913},[1881,2495,2496],{"class":1890},"`",[1881,2498,2021],{"class":2040},[1881,2500,2502,2504,2506,2508,2511],{"class":1883,"line":2501},23,[1881,2503,2478],{"class":1890},[1881,2505,2481],{"class":1935},[1881,2507,2128],{"class":2040},[1881,2509,2510],{"class":1894},"rawBody",[1881,2512,2021],{"class":2040},[1881,2514,2516,2518,2521,2523],{"class":1883,"line":2515},24,[1881,2517,2478],{"class":1890},[1881,2519,2520],{"class":1935},"digest",[1881,2522,2172],{"class":2040},[1881,2524,1920],{"class":1890},[1881,2526,2528],{"class":1883,"line":2527},25,[1881,2529,2530],{"class":2402},"  // Two v1 values appear for 24 h after a rotation; either secret verifies.\n",[1881,2532,2534,2537,2539,2541,2544],{"class":1883,"line":2533},26,[1881,2535,2536],{"class":1886},"  return",[1881,2538,2086],{"class":1894},[1881,2540,1656],{"class":1890},[1881,2542,2543],{"class":1935},"some",[1881,2545,1939],{"class":2040},[1881,2547,2549,2552,2555,2557],{"class":1883,"line":2548},27,[1881,2550,2551],{"class":1890},"    (",[1881,2553,2554],{"class":1944},"candidate",[1881,2556,2187],{"class":1890},[1881,2558,2559],{"class":1931}," =>\n",[1881,2561,2563,2566,2568,2570,2572,2574,2576,2578],{"class":1883,"line":2562},28,[1881,2564,2565],{"class":1894},"      candidate",[1881,2567,1656],{"class":1890},[1881,2569,2383],{"class":1894},[1881,2571,2202],{"class":1890},[1881,2573,2452],{"class":1894},[1881,2575,1656],{"class":1890},[1881,2577,2383],{"class":1894},[1881,2579,2580],{"class":1890}," &&\n",[1881,2582,2584,2587,2589,2591,2593,2595],{"class":1883,"line":2583},29,[1881,2585,2586],{"class":1935},"      timingSafeEqual",[1881,2588,2128],{"class":2040},[1881,2590,2554],{"class":1894},[1881,2592,1898],{"class":1890},[1881,2594,2452],{"class":1894},[1881,2596,2021],{"class":2040},[1881,2598,2600,2603],{"class":1883,"line":2599},30,[1881,2601,2602],{"class":2040},"  )",[1881,2604,1920],{"class":1890},[1881,2606,2608],{"class":1883,"line":2607},31,[1881,2609,2610],{"class":1890},"}\n",[1588,2612,2613,2614,2616,2617,2620,2621,1656],{},"Call it with the header value and the raw request body as a ",[1608,2615,2331],{},". It returns ",[1608,2618,2619],{},"false"," for a missing or malformed header, a timestamp more than five minutes from your clock, or a body or secret that does not match. For a complete Express server around it, see the ",[1768,2622,1083],{"href":2623},"/integrations-and-extensibility/webhooks/quickstart#nodejs-typescript-express",[1596,2625,2627],{"id":2626},"verification-in-other-languages","Verification in other languages",[1588,2629,2630,2631,2635],{},"Each function below follows the same steps as the reference implementation and has been checked against the ",[1768,2632,2634],{"href":2633},"#test-your-verifier","test vector"," at the end of this page.",[2637,2638,2640],"h3",{"id":2639},"python","Python",[1588,2642,2643],{},"Uses only the standard library.",[1843,2645,2648],{"className":2646,"code":2647,"language":2639,"meta":1849,"style":1849},"language-python shiki shiki-themes material-theme-lighter material-theme material-theme-palenight","import hashlib\nimport hmac\nimport re\nimport time\n\nTOLERANCE_SECONDS = 300\n\n\ndef verify_devstride_signature(signature_header, raw_body, secret, now=None):\n    \"\"\"True when any v1 signature matches HMAC-SHA256(secret, \"{t}.{raw_body}\")\n    and t is within 5 minutes of now. raw_body must be the exact bytes received.\"\"\"\n    if not signature_header:\n        return False\n    timestamp = None\n    signatures = []\n    for part in signature_header.split(\",\"):\n        key, _, value = part.strip().partition(\"=\")\n        if key == \"t\" and re.fullmatch(r\"[0-9]+\", value):\n            timestamp = int(value)\n        elif key == \"v1\" and re.fullmatch(r\"[0-9a-f]{64}\", value):\n            signatures.append(value)\n    if timestamp is None or not signatures:\n        return False\n    now = int(time.time()) if now is None else now\n    # Older (or newer) than 5 minutes: a replayed capture, or a clock to fix.\n    if abs(now - timestamp) > TOLERANCE_SECONDS:\n        return False\n    expected = hmac.new(\n        secret.encode(\"utf-8\"),\n        f\"{timestamp}.\".encode(\"utf-8\") + raw_body,\n        hashlib.sha256,\n    ).hexdigest()\n    # Two v1 values appear for 24 h after a rotation; either secret verifies.\n    return any(hmac.compare_digest(candidate, expected) for candidate in signatures)\n",[1608,2649,2650,2655,2660,2665,2670,2674,2679,2683,2687,2692,2697,2702,2707,2712,2717,2722,2727,2732,2737,2742,2747,2752,2757,2761,2766,2771,2776,2780,2785,2790,2795,2800,2806,2812],{"__ignoreMap":1849},[1881,2651,2652],{"class":1883,"line":17},[1881,2653,2654],{},"import hashlib\n",[1881,2656,2657],{"class":1883,"line":244},[1881,2658,2659],{},"import hmac\n",[1881,2661,2662],{"class":1883,"line":302},[1881,2663,2664],{},"import re\n",[1881,2666,2667],{"class":1883,"line":377},[1881,2668,2669],{},"import time\n",[1881,2671,2672],{"class":1883,"line":649},[1881,2673,1926],{"emptyLinePlaceholder":1925},[1881,2675,2676],{"class":1883,"line":659},[1881,2677,2678],{},"TOLERANCE_SECONDS = 300\n",[1881,2680,2681],{"class":1883,"line":596},[1881,2682,1926],{"emptyLinePlaceholder":1925},[1881,2684,2685],{"class":1883,"line":280},[1881,2686,1926],{"emptyLinePlaceholder":1925},[1881,2688,2689],{"class":1883,"line":905},[1881,2690,2691],{},"def verify_devstride_signature(signature_header, raw_body, secret, now=None):\n",[1881,2693,2694],{"class":1883,"line":817},[1881,2695,2696],{},"    \"\"\"True when any v1 signature matches HMAC-SHA256(secret, \"{t}.{raw_body}\")\n",[1881,2698,2699],{"class":1883,"line":1242},[1881,2700,2701],{},"    and t is within 5 minutes of now. raw_body must be the exact bytes received.\"\"\"\n",[1881,2703,2704],{"class":1883,"line":961},[1881,2705,2706],{},"    if not signature_header:\n",[1881,2708,2709],{"class":1883,"line":2143},[1881,2710,2711],{},"        return False\n",[1881,2713,2714],{"class":1883,"line":2192},[1881,2715,2716],{},"    timestamp = None\n",[1881,2718,2719],{"class":1883,"line":2263},[1881,2720,2721],{},"    signatures = []\n",[1881,2723,2724],{"class":1883,"line":2318},[1881,2725,2726],{},"    for part in signature_header.split(\",\"):\n",[1881,2728,2729],{"class":1883,"line":2357},[1881,2730,2731],{},"        key, _, value = part.strip().partition(\"=\")\n",[1881,2733,2734],{"class":1883,"line":2363},[1881,2735,2736],{},"        if key == \"t\" and re.fullmatch(r\"[0-9]+\", value):\n",[1881,2738,2739],{"class":1883,"line":2399},[1881,2740,2741],{},"            timestamp = int(value)\n",[1881,2743,2744],{"class":1883,"line":2406},[1881,2745,2746],{},"        elif key == \"v1\" and re.fullmatch(r\"[0-9a-f]{64}\", value):\n",[1881,2748,2749],{"class":1883,"line":2447},[1881,2750,2751],{},"            signatures.append(value)\n",[1881,2753,2754],{"class":1883,"line":2475},[1881,2755,2756],{},"    if timestamp is None or not signatures:\n",[1881,2758,2759],{"class":1883,"line":2501},[1881,2760,2711],{},[1881,2762,2763],{"class":1883,"line":2515},[1881,2764,2765],{},"    now = int(time.time()) if now is None else now\n",[1881,2767,2768],{"class":1883,"line":2527},[1881,2769,2770],{},"    # Older (or newer) than 5 minutes: a replayed capture, or a clock to fix.\n",[1881,2772,2773],{"class":1883,"line":2533},[1881,2774,2775],{},"    if abs(now - timestamp) > TOLERANCE_SECONDS:\n",[1881,2777,2778],{"class":1883,"line":2548},[1881,2779,2711],{},[1881,2781,2782],{"class":1883,"line":2562},[1881,2783,2784],{},"    expected = hmac.new(\n",[1881,2786,2787],{"class":1883,"line":2583},[1881,2788,2789],{},"        secret.encode(\"utf-8\"),\n",[1881,2791,2792],{"class":1883,"line":2599},[1881,2793,2794],{},"        f\"{timestamp}.\".encode(\"utf-8\") + raw_body,\n",[1881,2796,2797],{"class":1883,"line":2607},[1881,2798,2799],{},"        hashlib.sha256,\n",[1881,2801,2803],{"class":1883,"line":2802},32,[1881,2804,2805],{},"    ).hexdigest()\n",[1881,2807,2809],{"class":1883,"line":2808},33,[1881,2810,2811],{},"    # Two v1 values appear for 24 h after a rotation; either secret verifies.\n",[1881,2813,2815],{"class":1883,"line":2814},34,[1881,2816,2817],{},"    return any(hmac.compare_digest(candidate, expected) for candidate in signatures)\n",[1588,2819,2820,2821,2824,2825,2828,2829,2832,2833,1656],{},"In Flask pass ",[1608,2822,2823],{},"request.get_data()",", in Django ",[1608,2826,2827],{},"request.body",", in FastAPI or Starlette ",[1608,2830,2831],{},"await request.body()",". Full Flask and FastAPI receivers are in the ",[1768,2834,1083],{"href":2835},"/integrations-and-extensibility/webhooks/quickstart#python-flask",[2637,2837,2839],{"id":2838},"go","Go",[1843,2841,2844],{"className":2842,"code":2843,"language":2838,"meta":1849,"style":1849},"language-go shiki shiki-themes material-theme-lighter material-theme material-theme-palenight","package devstride\n\nimport (\n    \"crypto/hmac\"\n    \"crypto/sha256\"\n    \"encoding/hex\"\n    \"regexp\"\n    \"strconv\"\n    \"strings\"\n    \"time\"\n)\n\nvar (\n    digitsPattern    = regexp.MustCompile(`^[0-9]+$`)\n    signaturePattern = regexp.MustCompile(`^[0-9a-f]{64}$`)\n)\n\n// VerifyDevStrideSignature reports whether any v1 signature in header matches\n// HMAC-SHA256(secret, \"{t}.{rawBody}\") and t is within 5 minutes of now.\n// rawBody must be the exact bytes received.\nfunc VerifyDevStrideSignature(header string, rawBody []byte, secret string, now time.Time) bool {\n    timestamp := int64(-1)\n    var signatures [][]byte\n    for _, part := range strings.Split(header, \",\") {\n        key, value, _ := strings.Cut(strings.TrimSpace(part), \"=\")\n        switch {\n        case key == \"t\" && digitsPattern.MatchString(value):\n            if t, err := strconv.ParseInt(value, 10, 64); err == nil {\n                timestamp = t\n            }\n        case key == \"v1\" && signaturePattern.MatchString(value):\n            if sig, err := hex.DecodeString(value); err == nil {\n                signatures = append(signatures, sig)\n            }\n        }\n    }\n    if timestamp \u003C 0 || len(signatures) == 0 {\n        return false\n    }\n    // Older (or newer) than 5 minutes: a replayed capture, or a clock to fix.\n    if age := now.Unix() - timestamp; age > 300 || age \u003C -300 {\n        return false\n    }\n    mac := hmac.New(sha256.New, []byte(secret))\n    mac.Write([]byte(strconv.FormatInt(timestamp, 10) + \".\"))\n    mac.Write(rawBody)\n    expected := mac.Sum(nil)\n    // Two v1 values appear for 24 h after a rotation; either secret verifies.\n    for _, candidate := range signatures {\n        if hmac.Equal(candidate, expected) {\n            return true\n        }\n    }\n    return false\n}\n",[1608,2845,2846,2851,2855,2860,2865,2870,2875,2880,2885,2890,2895,2899,2903,2908,2913,2918,2922,2926,2931,2936,2941,2946,2951,2956,2961,2966,2971,2976,2981,2986,2991,2996,3001,3006,3010,3016,3022,3028,3034,3039,3045,3051,3056,3061,3067,3073,3079,3085,3091,3097,3103,3109,3114,3119,3125],{"__ignoreMap":1849},[1881,2847,2848],{"class":1883,"line":17},[1881,2849,2850],{},"package devstride\n",[1881,2852,2853],{"class":1883,"line":244},[1881,2854,1926],{"emptyLinePlaceholder":1925},[1881,2856,2857],{"class":1883,"line":302},[1881,2858,2859],{},"import (\n",[1881,2861,2862],{"class":1883,"line":377},[1881,2863,2864],{},"    \"crypto/hmac\"\n",[1881,2866,2867],{"class":1883,"line":649},[1881,2868,2869],{},"    \"crypto/sha256\"\n",[1881,2871,2872],{"class":1883,"line":659},[1881,2873,2874],{},"    \"encoding/hex\"\n",[1881,2876,2877],{"class":1883,"line":596},[1881,2878,2879],{},"    \"regexp\"\n",[1881,2881,2882],{"class":1883,"line":280},[1881,2883,2884],{},"    \"strconv\"\n",[1881,2886,2887],{"class":1883,"line":905},[1881,2888,2889],{},"    \"strings\"\n",[1881,2891,2892],{"class":1883,"line":817},[1881,2893,2894],{},"    \"time\"\n",[1881,2896,2897],{"class":1883,"line":1242},[1881,2898,2021],{},[1881,2900,2901],{"class":1883,"line":961},[1881,2902,1926],{"emptyLinePlaceholder":1925},[1881,2904,2905],{"class":1883,"line":2143},[1881,2906,2907],{},"var (\n",[1881,2909,2910],{"class":1883,"line":2192},[1881,2911,2912],{},"    digitsPattern    = regexp.MustCompile(`^[0-9]+$`)\n",[1881,2914,2915],{"class":1883,"line":2263},[1881,2916,2917],{},"    signaturePattern = regexp.MustCompile(`^[0-9a-f]{64}$`)\n",[1881,2919,2920],{"class":1883,"line":2318},[1881,2921,2021],{},[1881,2923,2924],{"class":1883,"line":2357},[1881,2925,1926],{"emptyLinePlaceholder":1925},[1881,2927,2928],{"class":1883,"line":2363},[1881,2929,2930],{},"// VerifyDevStrideSignature reports whether any v1 signature in header matches\n",[1881,2932,2933],{"class":1883,"line":2399},[1881,2934,2935],{},"// HMAC-SHA256(secret, \"{t}.{rawBody}\") and t is within 5 minutes of now.\n",[1881,2937,2938],{"class":1883,"line":2406},[1881,2939,2940],{},"// rawBody must be the exact bytes received.\n",[1881,2942,2943],{"class":1883,"line":2447},[1881,2944,2945],{},"func VerifyDevStrideSignature(header string, rawBody []byte, secret string, now time.Time) bool {\n",[1881,2947,2948],{"class":1883,"line":2475},[1881,2949,2950],{},"    timestamp := int64(-1)\n",[1881,2952,2953],{"class":1883,"line":2501},[1881,2954,2955],{},"    var signatures [][]byte\n",[1881,2957,2958],{"class":1883,"line":2515},[1881,2959,2960],{},"    for _, part := range strings.Split(header, \",\") {\n",[1881,2962,2963],{"class":1883,"line":2527},[1881,2964,2965],{},"        key, value, _ := strings.Cut(strings.TrimSpace(part), \"=\")\n",[1881,2967,2968],{"class":1883,"line":2533},[1881,2969,2970],{},"        switch {\n",[1881,2972,2973],{"class":1883,"line":2548},[1881,2974,2975],{},"        case key == \"t\" && digitsPattern.MatchString(value):\n",[1881,2977,2978],{"class":1883,"line":2562},[1881,2979,2980],{},"            if t, err := strconv.ParseInt(value, 10, 64); err == nil {\n",[1881,2982,2983],{"class":1883,"line":2583},[1881,2984,2985],{},"                timestamp = t\n",[1881,2987,2988],{"class":1883,"line":2599},[1881,2989,2990],{},"            }\n",[1881,2992,2993],{"class":1883,"line":2607},[1881,2994,2995],{},"        case key == \"v1\" && signaturePattern.MatchString(value):\n",[1881,2997,2998],{"class":1883,"line":2802},[1881,2999,3000],{},"            if sig, err := hex.DecodeString(value); err == nil {\n",[1881,3002,3003],{"class":1883,"line":2808},[1881,3004,3005],{},"                signatures = append(signatures, sig)\n",[1881,3007,3008],{"class":1883,"line":2814},[1881,3009,2990],{},[1881,3011,3013],{"class":1883,"line":3012},35,[1881,3014,3015],{},"        }\n",[1881,3017,3019],{"class":1883,"line":3018},36,[1881,3020,3021],{},"    }\n",[1881,3023,3025],{"class":1883,"line":3024},37,[1881,3026,3027],{},"    if timestamp \u003C 0 || len(signatures) == 0 {\n",[1881,3029,3031],{"class":1883,"line":3030},38,[1881,3032,3033],{},"        return false\n",[1881,3035,3037],{"class":1883,"line":3036},39,[1881,3038,3021],{},[1881,3040,3042],{"class":1883,"line":3041},40,[1881,3043,3044],{},"    // Older (or newer) than 5 minutes: a replayed capture, or a clock to fix.\n",[1881,3046,3048],{"class":1883,"line":3047},41,[1881,3049,3050],{},"    if age := now.Unix() - timestamp; age > 300 || age \u003C -300 {\n",[1881,3052,3054],{"class":1883,"line":3053},42,[1881,3055,3033],{},[1881,3057,3059],{"class":1883,"line":3058},43,[1881,3060,3021],{},[1881,3062,3064],{"class":1883,"line":3063},44,[1881,3065,3066],{},"    mac := hmac.New(sha256.New, []byte(secret))\n",[1881,3068,3070],{"class":1883,"line":3069},45,[1881,3071,3072],{},"    mac.Write([]byte(strconv.FormatInt(timestamp, 10) + \".\"))\n",[1881,3074,3076],{"class":1883,"line":3075},46,[1881,3077,3078],{},"    mac.Write(rawBody)\n",[1881,3080,3082],{"class":1883,"line":3081},47,[1881,3083,3084],{},"    expected := mac.Sum(nil)\n",[1881,3086,3088],{"class":1883,"line":3087},48,[1881,3089,3090],{},"    // Two v1 values appear for 24 h after a rotation; either secret verifies.\n",[1881,3092,3094],{"class":1883,"line":3093},49,[1881,3095,3096],{},"    for _, candidate := range signatures {\n",[1881,3098,3100],{"class":1883,"line":3099},50,[1881,3101,3102],{},"        if hmac.Equal(candidate, expected) {\n",[1881,3104,3106],{"class":1883,"line":3105},51,[1881,3107,3108],{},"            return true\n",[1881,3110,3112],{"class":1883,"line":3111},52,[1881,3113,3015],{},[1881,3115,3117],{"class":1883,"line":3116},53,[1881,3118,3021],{},[1881,3120,3122],{"class":1883,"line":3121},54,[1881,3123,3124],{},"    return false\n",[1881,3126,3128],{"class":1883,"line":3127},55,[1881,3129,2610],{},[1588,3131,3132,3133,3136,3137,1948],{},"In a ",[1608,3134,3135],{},"net/http"," handler, read the body before anything else and pass ",[1608,3138,3139],{},"time.Now()",[1843,3141,3143],{"className":2842,"code":3142,"language":2838,"meta":1849,"style":1849},"body, err := io.ReadAll(io.LimitReader(r.Body, 1\u003C\u003C20))\nif err != nil || !VerifyDevStrideSignature(r.Header.Get(\"X-DevStride-Signature\"), body, secret, time.Now()) {\n    http.Error(w, \"invalid signature\", http.StatusUnauthorized)\n    return\n}\nw.WriteHeader(http.StatusNoContent)\n// then decode body with json.Unmarshal and hand the event to a worker\n",[1608,3144,3145,3150,3155,3160,3165,3169,3174],{"__ignoreMap":1849},[1881,3146,3147],{"class":1883,"line":17},[1881,3148,3149],{},"body, err := io.ReadAll(io.LimitReader(r.Body, 1\u003C\u003C20))\n",[1881,3151,3152],{"class":1883,"line":244},[1881,3153,3154],{},"if err != nil || !VerifyDevStrideSignature(r.Header.Get(\"X-DevStride-Signature\"), body, secret, time.Now()) {\n",[1881,3156,3157],{"class":1883,"line":302},[1881,3158,3159],{},"    http.Error(w, \"invalid signature\", http.StatusUnauthorized)\n",[1881,3161,3162],{"class":1883,"line":377},[1881,3163,3164],{},"    return\n",[1881,3166,3167],{"class":1883,"line":649},[1881,3168,2610],{},[1881,3170,3171],{"class":1883,"line":659},[1881,3172,3173],{},"w.WriteHeader(http.StatusNoContent)\n",[1881,3175,3176],{"class":1883,"line":596},[1881,3177,3178],{},"// then decode body with json.Unmarshal and hand the event to a worker\n",[2637,3180,3182],{"id":3181},"ruby","Ruby",[1843,3184,3187],{"className":3185,"code":3186,"language":3181,"meta":1849,"style":1849},"language-ruby shiki shiki-themes material-theme-lighter material-theme material-theme-palenight","require \"openssl\"\n\nTOLERANCE_SECONDS = 300\n\n# True when any v1 signature matches HMAC-SHA256(secret, \"{t}.{raw_body}\") and\n# t is within 5 minutes of now. raw_body must be the exact bytes received.\ndef verify_devstride_signature(header, raw_body, secret, now: Time.now.to_i)\n  return false if header.nil? || header.empty?\n\n  timestamp = nil\n  signatures = []\n  header.split(\",\").each do |part|\n    key, value = part.strip.split(\"=\", 2)\n    if key == \"t\" && value&.match?(/\\A[0-9]+\\z/)\n      timestamp = value.to_i\n    elsif key == \"v1\" && value&.match?(/\\A[0-9a-f]{64}\\z/)\n      signatures \u003C\u003C value\n    end\n  end\n  return false if timestamp.nil? || signatures.empty?\n  # Older (or newer) than 5 minutes: a replayed capture, or a clock to fix.\n  return false if (now - timestamp).abs > TOLERANCE_SECONDS\n\n  expected = OpenSSL::HMAC.hexdigest(\"SHA256\", secret, \"#{timestamp}.#{raw_body}\")\n  # Two v1 values appear for 24 h after a rotation; either secret verifies.\n  signatures.any? { |candidate| secure_compare(candidate, expected) }\nend\n\n# Constant-time comparison of two equal-length strings.\ndef secure_compare(a, b)\n  return false unless a.bytesize == b.bytesize\n\n  a.bytes.zip(b.bytes).reduce(0) { |acc, (x, y)| acc | (x ^ y) }.zero?\nend\n",[1608,3188,3189,3194,3198,3202,3206,3211,3216,3221,3226,3230,3235,3240,3245,3250,3255,3260,3265,3270,3275,3280,3285,3290,3295,3299,3304,3309,3314,3319,3323,3328,3333,3338,3342,3347],{"__ignoreMap":1849},[1881,3190,3191],{"class":1883,"line":17},[1881,3192,3193],{},"require \"openssl\"\n",[1881,3195,3196],{"class":1883,"line":244},[1881,3197,1926],{"emptyLinePlaceholder":1925},[1881,3199,3200],{"class":1883,"line":302},[1881,3201,2678],{},[1881,3203,3204],{"class":1883,"line":377},[1881,3205,1926],{"emptyLinePlaceholder":1925},[1881,3207,3208],{"class":1883,"line":649},[1881,3209,3210],{},"# True when any v1 signature matches HMAC-SHA256(secret, \"{t}.{raw_body}\") and\n",[1881,3212,3213],{"class":1883,"line":659},[1881,3214,3215],{},"# t is within 5 minutes of now. raw_body must be the exact bytes received.\n",[1881,3217,3218],{"class":1883,"line":596},[1881,3219,3220],{},"def verify_devstride_signature(header, raw_body, secret, now: Time.now.to_i)\n",[1881,3222,3223],{"class":1883,"line":280},[1881,3224,3225],{},"  return false if header.nil? || header.empty?\n",[1881,3227,3228],{"class":1883,"line":905},[1881,3229,1926],{"emptyLinePlaceholder":1925},[1881,3231,3232],{"class":1883,"line":817},[1881,3233,3234],{},"  timestamp = nil\n",[1881,3236,3237],{"class":1883,"line":1242},[1881,3238,3239],{},"  signatures = []\n",[1881,3241,3242],{"class":1883,"line":961},[1881,3243,3244],{},"  header.split(\",\").each do |part|\n",[1881,3246,3247],{"class":1883,"line":2143},[1881,3248,3249],{},"    key, value = part.strip.split(\"=\", 2)\n",[1881,3251,3252],{"class":1883,"line":2192},[1881,3253,3254],{},"    if key == \"t\" && value&.match?(/\\A[0-9]+\\z/)\n",[1881,3256,3257],{"class":1883,"line":2263},[1881,3258,3259],{},"      timestamp = value.to_i\n",[1881,3261,3262],{"class":1883,"line":2318},[1881,3263,3264],{},"    elsif key == \"v1\" && value&.match?(/\\A[0-9a-f]{64}\\z/)\n",[1881,3266,3267],{"class":1883,"line":2357},[1881,3268,3269],{},"      signatures \u003C\u003C value\n",[1881,3271,3272],{"class":1883,"line":2363},[1881,3273,3274],{},"    end\n",[1881,3276,3277],{"class":1883,"line":2399},[1881,3278,3279],{},"  end\n",[1881,3281,3282],{"class":1883,"line":2406},[1881,3283,3284],{},"  return false if timestamp.nil? || signatures.empty?\n",[1881,3286,3287],{"class":1883,"line":2447},[1881,3288,3289],{},"  # Older (or newer) than 5 minutes: a replayed capture, or a clock to fix.\n",[1881,3291,3292],{"class":1883,"line":2475},[1881,3293,3294],{},"  return false if (now - timestamp).abs > TOLERANCE_SECONDS\n",[1881,3296,3297],{"class":1883,"line":2501},[1881,3298,1926],{"emptyLinePlaceholder":1925},[1881,3300,3301],{"class":1883,"line":2515},[1881,3302,3303],{},"  expected = OpenSSL::HMAC.hexdigest(\"SHA256\", secret, \"#{timestamp}.#{raw_body}\")\n",[1881,3305,3306],{"class":1883,"line":2527},[1881,3307,3308],{},"  # Two v1 values appear for 24 h after a rotation; either secret verifies.\n",[1881,3310,3311],{"class":1883,"line":2533},[1881,3312,3313],{},"  signatures.any? { |candidate| secure_compare(candidate, expected) }\n",[1881,3315,3316],{"class":1883,"line":2548},[1881,3317,3318],{},"end\n",[1881,3320,3321],{"class":1883,"line":2562},[1881,3322,1926],{"emptyLinePlaceholder":1925},[1881,3324,3325],{"class":1883,"line":2583},[1881,3326,3327],{},"# Constant-time comparison of two equal-length strings.\n",[1881,3329,3330],{"class":1883,"line":2599},[1881,3331,3332],{},"def secure_compare(a, b)\n",[1881,3334,3335],{"class":1883,"line":2607},[1881,3336,3337],{},"  return false unless a.bytesize == b.bytesize\n",[1881,3339,3340],{"class":1883,"line":2802},[1881,3341,1926],{"emptyLinePlaceholder":1925},[1881,3343,3344],{"class":1883,"line":2808},[1881,3345,3346],{},"  a.bytes.zip(b.bytes).reduce(0) { |acc, (x, y)| acc | (x ^ y) }.zero?\n",[1881,3348,3349],{"class":1883,"line":2814},[1881,3350,3318],{},[1588,3352,3353,3354,3357,3358,3361,3362,3365,3366,3369],{},"In Rails pass ",[1608,3355,3356],{},"request.raw_post","; in Sinatra or plain Rack, ",[1608,3359,3360],{},"request.body.read"," (call ",[1608,3363,3364],{},"request.body.rewind"," first if something else may have read it). In Rails you can use ",[1608,3367,3368],{},"ActiveSupport::SecurityUtils.secure_compare"," instead of the helper.",[2637,3371,3373],{"id":3372},"php","PHP",[1843,3375,3378],{"className":3376,"code":3377,"language":3372,"meta":1849,"style":1849},"language-php shiki shiki-themes material-theme-lighter material-theme material-theme-palenight","\u003C?php\n\n/**\n * True when any v1 signature matches HMAC-SHA256($secret, \"{t}.{$rawBody}\")\n * and t is within 5 minutes of now. $rawBody must be the exact bytes received.\n */\nfunction verifyDevStrideSignature(?string $header, string $rawBody, string $secret, ?int $now = null): bool\n{\n    if ($header === null || $header === '') {\n        return false;\n    }\n    $timestamp = null;\n    $signatures = [];\n    foreach (explode(',', $header) as $part) {\n        $pair = explode('=', trim($part), 2);\n        if (count($pair) !== 2) {\n            continue;\n        }\n        [$key, $value] = $pair;\n        if ($key === 't' && preg_match('/\\A[0-9]+\\z/', $value)) {\n            $timestamp = (int) $value;\n        } elseif ($key === 'v1' && preg_match('/\\A[0-9a-f]{64}\\z/', $value)) {\n            $signatures[] = $value;\n        }\n    }\n    if ($timestamp === null || $signatures === []) {\n        return false;\n    }\n    // Older (or newer) than 5 minutes: a replayed capture, or a clock to fix.\n    if (abs(($now ?? time()) - $timestamp) > 300) {\n        return false;\n    }\n    $expected = hash_hmac('sha256', $timestamp . '.' . $rawBody, $secret);\n    // Two v1 values appear for 24 h after a rotation; either secret verifies.\n    foreach ($signatures as $candidate) {\n        if (hash_equals($expected, $candidate)) {\n            return true;\n        }\n    }\n    return false;\n}\n",[1608,3379,3380,3385,3389,3394,3399,3404,3409,3414,3418,3423,3428,3432,3437,3442,3447,3452,3457,3462,3466,3471,3476,3481,3486,3491,3495,3499,3504,3508,3512,3516,3521,3525,3529,3534,3538,3543,3548,3553,3557,3561,3566],{"__ignoreMap":1849},[1881,3381,3382],{"class":1883,"line":17},[1881,3383,3384],{},"\u003C?php\n",[1881,3386,3387],{"class":1883,"line":244},[1881,3388,1926],{"emptyLinePlaceholder":1925},[1881,3390,3391],{"class":1883,"line":302},[1881,3392,3393],{},"/**\n",[1881,3395,3396],{"class":1883,"line":377},[1881,3397,3398],{}," * True when any v1 signature matches HMAC-SHA256($secret, \"{t}.{$rawBody}\")\n",[1881,3400,3401],{"class":1883,"line":649},[1881,3402,3403],{}," * and t is within 5 minutes of now. $rawBody must be the exact bytes received.\n",[1881,3405,3406],{"class":1883,"line":659},[1881,3407,3408],{}," */\n",[1881,3410,3411],{"class":1883,"line":596},[1881,3412,3413],{},"function verifyDevStrideSignature(?string $header, string $rawBody, string $secret, ?int $now = null): bool\n",[1881,3415,3416],{"class":1883,"line":280},[1881,3417,2140],{},[1881,3419,3420],{"class":1883,"line":905},[1881,3421,3422],{},"    if ($header === null || $header === '') {\n",[1881,3424,3425],{"class":1883,"line":817},[1881,3426,3427],{},"        return false;\n",[1881,3429,3430],{"class":1883,"line":1242},[1881,3431,3021],{},[1881,3433,3434],{"class":1883,"line":961},[1881,3435,3436],{},"    $timestamp = null;\n",[1881,3438,3439],{"class":1883,"line":2143},[1881,3440,3441],{},"    $signatures = [];\n",[1881,3443,3444],{"class":1883,"line":2192},[1881,3445,3446],{},"    foreach (explode(',', $header) as $part) {\n",[1881,3448,3449],{"class":1883,"line":2263},[1881,3450,3451],{},"        $pair = explode('=', trim($part), 2);\n",[1881,3453,3454],{"class":1883,"line":2318},[1881,3455,3456],{},"        if (count($pair) !== 2) {\n",[1881,3458,3459],{"class":1883,"line":2357},[1881,3460,3461],{},"            continue;\n",[1881,3463,3464],{"class":1883,"line":2363},[1881,3465,3015],{},[1881,3467,3468],{"class":1883,"line":2399},[1881,3469,3470],{},"        [$key, $value] = $pair;\n",[1881,3472,3473],{"class":1883,"line":2406},[1881,3474,3475],{},"        if ($key === 't' && preg_match('/\\A[0-9]+\\z/', $value)) {\n",[1881,3477,3478],{"class":1883,"line":2447},[1881,3479,3480],{},"            $timestamp = (int) $value;\n",[1881,3482,3483],{"class":1883,"line":2475},[1881,3484,3485],{},"        } elseif ($key === 'v1' && preg_match('/\\A[0-9a-f]{64}\\z/', $value)) {\n",[1881,3487,3488],{"class":1883,"line":2501},[1881,3489,3490],{},"            $signatures[] = $value;\n",[1881,3492,3493],{"class":1883,"line":2515},[1881,3494,3015],{},[1881,3496,3497],{"class":1883,"line":2527},[1881,3498,3021],{},[1881,3500,3501],{"class":1883,"line":2533},[1881,3502,3503],{},"    if ($timestamp === null || $signatures === []) {\n",[1881,3505,3506],{"class":1883,"line":2548},[1881,3507,3427],{},[1881,3509,3510],{"class":1883,"line":2562},[1881,3511,3021],{},[1881,3513,3514],{"class":1883,"line":2583},[1881,3515,3044],{},[1881,3517,3518],{"class":1883,"line":2599},[1881,3519,3520],{},"    if (abs(($now ?? time()) - $timestamp) > 300) {\n",[1881,3522,3523],{"class":1883,"line":2607},[1881,3524,3427],{},[1881,3526,3527],{"class":1883,"line":2802},[1881,3528,3021],{},[1881,3530,3531],{"class":1883,"line":2808},[1881,3532,3533],{},"    $expected = hash_hmac('sha256', $timestamp . '.' . $rawBody, $secret);\n",[1881,3535,3536],{"class":1883,"line":2814},[1881,3537,3090],{},[1881,3539,3540],{"class":1883,"line":3012},[1881,3541,3542],{},"    foreach ($signatures as $candidate) {\n",[1881,3544,3545],{"class":1883,"line":3018},[1881,3546,3547],{},"        if (hash_equals($expected, $candidate)) {\n",[1881,3549,3550],{"class":1883,"line":3024},[1881,3551,3552],{},"            return true;\n",[1881,3554,3555],{"class":1883,"line":3030},[1881,3556,3015],{},[1881,3558,3559],{"class":1883,"line":3036},[1881,3560,3021],{},[1881,3562,3563],{"class":1883,"line":3041},[1881,3564,3565],{},"    return false;\n",[1881,3567,3568],{"class":1883,"line":3047},[1881,3569,2610],{},[1588,3571,3572,3573,3576,3577,3580,3581,3584,3585,1656],{},"Read the raw body with ",[1608,3574,3575],{},"file_get_contents('php://input')"," and the header with ",[1608,3578,3579],{},"$_SERVER['HTTP_X_DEVSTRIDE_SIGNATURE'] ?? null",". In Laravel use ",[1608,3582,3583],{},"$request->getContent()"," and ",[1608,3586,3587],{},"$request->header('X-DevStride-Signature')",[2637,3589,3591],{"id":3590},"c","C#",[1588,3593,3594],{},"For .NET 6 and later.",[1843,3596,3600],{"className":3597,"code":3598,"language":3599,"meta":1849,"style":1849},"language-csharp shiki shiki-themes material-theme-lighter material-theme material-theme-palenight","using System;\nusing System.Collections.Generic;\nusing System.Linq;\nusing System.Security.Cryptography;\nusing System.Text;\nusing System.Text.RegularExpressions;\n\npublic static class DevStrideWebhooks\n{\n    private static readonly Regex Digits = new(@\"\\A[0-9]+\\z\");\n    private static readonly Regex Signature = new(@\"\\A[0-9a-f]{64}\\z\");\n\n    /// True when any v1 signature matches HMAC-SHA256(secret, \"{t}.{rawBody}\")\n    /// and t is within 5 minutes of now. rawBody must be the exact bytes received.\n    public static bool VerifySignature(string? header, byte[] rawBody, string secret, long? nowSeconds = null)\n    {\n        if (string.IsNullOrEmpty(header)) return false;\n        long? timestamp = null;\n        var signatures = new List\u003Cbyte[]>();\n        foreach (var part in header.Split(','))\n        {\n            var pair = part.Trim().Split('=', 2);\n            if (pair.Length != 2) continue;\n            if (pair[0] == \"t\" && Digits.IsMatch(pair[1]) && long.TryParse(pair[1], out var t))\n                timestamp = t;\n            else if (pair[0] == \"v1\" && Signature.IsMatch(pair[1]))\n                signatures.Add(Convert.FromHexString(pair[1]));\n        }\n        if (timestamp is null || signatures.Count == 0) return false;\n\n        // Older (or newer) than 5 minutes: a replayed capture, or a clock to fix.\n        var now = nowSeconds ?? DateTimeOffset.UtcNow.ToUnixTimeSeconds();\n        if (Math.Abs(now - timestamp.Value) > 300) return false;\n\n        var prefix = Encoding.UTF8.GetBytes($\"{timestamp.Value}.\");\n        var signed = new byte[prefix.Length + rawBody.Length];\n        prefix.CopyTo(signed, 0);\n        rawBody.CopyTo(signed, prefix.Length);\n        var expected = HMACSHA256.HashData(Encoding.UTF8.GetBytes(secret), signed);\n\n        // Two v1 values appear for 24 h after a rotation; either secret verifies.\n        return signatures.Any(candidate => CryptographicOperations.FixedTimeEquals(candidate, expected));\n    }\n}\n","csharp",[1608,3601,3602,3607,3612,3617,3622,3627,3632,3636,3641,3645,3650,3655,3659,3664,3669,3674,3679,3684,3689,3694,3699,3704,3709,3714,3719,3724,3729,3734,3738,3743,3747,3752,3757,3762,3766,3771,3776,3781,3786,3791,3795,3800,3805,3809],{"__ignoreMap":1849},[1881,3603,3604],{"class":1883,"line":17},[1881,3605,3606],{},"using System;\n",[1881,3608,3609],{"class":1883,"line":244},[1881,3610,3611],{},"using System.Collections.Generic;\n",[1881,3613,3614],{"class":1883,"line":302},[1881,3615,3616],{},"using System.Linq;\n",[1881,3618,3619],{"class":1883,"line":377},[1881,3620,3621],{},"using System.Security.Cryptography;\n",[1881,3623,3624],{"class":1883,"line":649},[1881,3625,3626],{},"using System.Text;\n",[1881,3628,3629],{"class":1883,"line":659},[1881,3630,3631],{},"using System.Text.RegularExpressions;\n",[1881,3633,3634],{"class":1883,"line":596},[1881,3635,1926],{"emptyLinePlaceholder":1925},[1881,3637,3638],{"class":1883,"line":280},[1881,3639,3640],{},"public static class DevStrideWebhooks\n",[1881,3642,3643],{"class":1883,"line":905},[1881,3644,2140],{},[1881,3646,3647],{"class":1883,"line":817},[1881,3648,3649],{},"    private static readonly Regex Digits = new(@\"\\A[0-9]+\\z\");\n",[1881,3651,3652],{"class":1883,"line":1242},[1881,3653,3654],{},"    private static readonly Regex Signature = new(@\"\\A[0-9a-f]{64}\\z\");\n",[1881,3656,3657],{"class":1883,"line":961},[1881,3658,1926],{"emptyLinePlaceholder":1925},[1881,3660,3661],{"class":1883,"line":2143},[1881,3662,3663],{},"    /// True when any v1 signature matches HMAC-SHA256(secret, \"{t}.{rawBody}\")\n",[1881,3665,3666],{"class":1883,"line":2192},[1881,3667,3668],{},"    /// and t is within 5 minutes of now. rawBody must be the exact bytes received.\n",[1881,3670,3671],{"class":1883,"line":2263},[1881,3672,3673],{},"    public static bool VerifySignature(string? header, byte[] rawBody, string secret, long? nowSeconds = null)\n",[1881,3675,3676],{"class":1883,"line":2318},[1881,3677,3678],{},"    {\n",[1881,3680,3681],{"class":1883,"line":2357},[1881,3682,3683],{},"        if (string.IsNullOrEmpty(header)) return false;\n",[1881,3685,3686],{"class":1883,"line":2363},[1881,3687,3688],{},"        long? timestamp = null;\n",[1881,3690,3691],{"class":1883,"line":2399},[1881,3692,3693],{},"        var signatures = new List\u003Cbyte[]>();\n",[1881,3695,3696],{"class":1883,"line":2406},[1881,3697,3698],{},"        foreach (var part in header.Split(','))\n",[1881,3700,3701],{"class":1883,"line":2447},[1881,3702,3703],{},"        {\n",[1881,3705,3706],{"class":1883,"line":2475},[1881,3707,3708],{},"            var pair = part.Trim().Split('=', 2);\n",[1881,3710,3711],{"class":1883,"line":2501},[1881,3712,3713],{},"            if (pair.Length != 2) continue;\n",[1881,3715,3716],{"class":1883,"line":2515},[1881,3717,3718],{},"            if (pair[0] == \"t\" && Digits.IsMatch(pair[1]) && long.TryParse(pair[1], out var t))\n",[1881,3720,3721],{"class":1883,"line":2527},[1881,3722,3723],{},"                timestamp = t;\n",[1881,3725,3726],{"class":1883,"line":2533},[1881,3727,3728],{},"            else if (pair[0] == \"v1\" && Signature.IsMatch(pair[1]))\n",[1881,3730,3731],{"class":1883,"line":2548},[1881,3732,3733],{},"                signatures.Add(Convert.FromHexString(pair[1]));\n",[1881,3735,3736],{"class":1883,"line":2562},[1881,3737,3015],{},[1881,3739,3740],{"class":1883,"line":2583},[1881,3741,3742],{},"        if (timestamp is null || signatures.Count == 0) return false;\n",[1881,3744,3745],{"class":1883,"line":2599},[1881,3746,1926],{"emptyLinePlaceholder":1925},[1881,3748,3749],{"class":1883,"line":2607},[1881,3750,3751],{},"        // Older (or newer) than 5 minutes: a replayed capture, or a clock to fix.\n",[1881,3753,3754],{"class":1883,"line":2802},[1881,3755,3756],{},"        var now = nowSeconds ?? DateTimeOffset.UtcNow.ToUnixTimeSeconds();\n",[1881,3758,3759],{"class":1883,"line":2808},[1881,3760,3761],{},"        if (Math.Abs(now - timestamp.Value) > 300) return false;\n",[1881,3763,3764],{"class":1883,"line":2814},[1881,3765,1926],{"emptyLinePlaceholder":1925},[1881,3767,3768],{"class":1883,"line":3012},[1881,3769,3770],{},"        var prefix = Encoding.UTF8.GetBytes($\"{timestamp.Value}.\");\n",[1881,3772,3773],{"class":1883,"line":3018},[1881,3774,3775],{},"        var signed = new byte[prefix.Length + rawBody.Length];\n",[1881,3777,3778],{"class":1883,"line":3024},[1881,3779,3780],{},"        prefix.CopyTo(signed, 0);\n",[1881,3782,3783],{"class":1883,"line":3030},[1881,3784,3785],{},"        rawBody.CopyTo(signed, prefix.Length);\n",[1881,3787,3788],{"class":1883,"line":3036},[1881,3789,3790],{},"        var expected = HMACSHA256.HashData(Encoding.UTF8.GetBytes(secret), signed);\n",[1881,3792,3793],{"class":1883,"line":3041},[1881,3794,1926],{"emptyLinePlaceholder":1925},[1881,3796,3797],{"class":1883,"line":3047},[1881,3798,3799],{},"        // Two v1 values appear for 24 h after a rotation; either secret verifies.\n",[1881,3801,3802],{"class":1883,"line":3053},[1881,3803,3804],{},"        return signatures.Any(candidate => CryptographicOperations.FixedTimeEquals(candidate, expected));\n",[1881,3806,3807],{"class":1883,"line":3058},[1881,3808,3021],{},[1881,3810,3811],{"class":1883,"line":3063},[1881,3812,2610],{},[1588,3814,3815],{},"In ASP.NET Core, copy the body into a byte array before model binding touches it:",[1843,3817,3819],{"className":3597,"code":3818,"language":3599,"meta":1849,"style":1849},"app.MapPost(\"/devstride/webhooks\", async (HttpRequest request) =>\n{\n    using var buffer = new MemoryStream();\n    await request.Body.CopyToAsync(buffer);\n    var rawBody = buffer.ToArray();\n    if (!DevStrideWebhooks.VerifySignature(request.Headers[\"X-DevStride-Signature\"].ToString(), rawBody, secret))\n        return Results.Unauthorized();\n    // parse rawBody with System.Text.Json and queue the event for processing\n    return Results.NoContent();\n});\n",[1608,3820,3821,3826,3830,3835,3840,3845,3850,3855,3860,3865],{"__ignoreMap":1849},[1881,3822,3823],{"class":1883,"line":17},[1881,3824,3825],{},"app.MapPost(\"/devstride/webhooks\", async (HttpRequest request) =>\n",[1881,3827,3828],{"class":1883,"line":244},[1881,3829,2140],{},[1881,3831,3832],{"class":1883,"line":302},[1881,3833,3834],{},"    using var buffer = new MemoryStream();\n",[1881,3836,3837],{"class":1883,"line":377},[1881,3838,3839],{},"    await request.Body.CopyToAsync(buffer);\n",[1881,3841,3842],{"class":1883,"line":649},[1881,3843,3844],{},"    var rawBody = buffer.ToArray();\n",[1881,3846,3847],{"class":1883,"line":659},[1881,3848,3849],{},"    if (!DevStrideWebhooks.VerifySignature(request.Headers[\"X-DevStride-Signature\"].ToString(), rawBody, secret))\n",[1881,3851,3852],{"class":1883,"line":596},[1881,3853,3854],{},"        return Results.Unauthorized();\n",[1881,3856,3857],{"class":1883,"line":280},[1881,3858,3859],{},"    // parse rawBody with System.Text.Json and queue the event for processing\n",[1881,3861,3862],{"class":1883,"line":905},[1881,3863,3864],{},"    return Results.NoContent();\n",[1881,3866,3867],{"class":1883,"line":817},[1881,3868,3869],{},"});\n",[1596,3871,3873],{"id":3872},"replay-protection","Replay protection",[1588,3875,3876],{},"Two checks together stop an attacker who captured a real request from sending it to you again:",[3878,3879,3880,3893],"ul",{},[1616,3881,3882,3885,3886,3888,3889,3892],{},[1592,3883,3884],{},"The timestamp window."," Reject a request whose ",[1608,3887,1621],{}," is more than ",[1592,3890,3891],{},"300 seconds (5 minutes)"," away from your clock, in either direction. Every attempt is signed fresh with the time it was sent, so a retry that arrives hours after the change still verifies; only a stale copy fails. Keep your server's clock synchronized (NTP).",[1616,3894,3895,3898,3899,3901,3902,1656],{},[1592,3896,3897],{},"The event id."," Inside the window a captured request still verifies, so also record each ",[1608,3900,1739],{}," you have processed and skip repeats. You need this anyway, because DevStride itself can deliver an event more than once; see ",[1768,3903,3905],{"href":3904},"/integrations-and-extensibility/webhooks/delivery-retries-and-ordering#at-least-once-delivery","At-least-once delivery",[1596,3907,3909],{"id":3908},"constant-time-comparison","Constant-time comparison",[1588,3911,3912,3913,3916,3917,3920,3921,3924,3925,3928,3929,3932,3933,3936],{},"Compare signatures with your language's constant-time function: ",[1608,3914,3915],{},"crypto.timingSafeEqual"," (Node.js), ",[1608,3918,3919],{},"hmac.compare_digest"," (Python), ",[1608,3922,3923],{},"hmac.Equal"," (Go), ",[1608,3926,3927],{},"hash_equals"," (PHP), ",[1608,3930,3931],{},"CryptographicOperations.FixedTimeEquals"," (.NET). An ordinary ",[1608,3934,3935],{},"=="," returns as soon as one character differs, and the time that takes leaks, a little at a time, how much of a forged signature was right.",[1596,3938,3940],{"id":3939},"secret-rotation","Secret rotation",[1588,3942,3943,3944,3947,3948,3951,3952,3955,3956,3958],{},"When you ",[1592,3945,3946],{},"Rotate secret"," (see ",[1768,3949,1099],{"href":3950},"/integrations-and-extensibility/webhooks/managing-endpoints#rotate-the-signing-secret","), DevStride signs every delivery with ",[1592,3953,3954],{},"both"," the new secret and the previous one for 24 hours, so the header carries two ",[1608,3957,1662],{}," values, the new secret's first:",[1843,3960,3963],{"className":3961,"code":3962,"language":1848,"meta":1849},[1846],"X-DevStride-Signature: t=1760709791,v1=\u003Csigned with the new secret>,v1=\u003Csigned with the previous secret>\n",[1608,3964,3962],{"__ignoreMap":1849},[1588,3966,3967,3968,3970],{},"Because your verifier accepts any matching ",[1608,3969,1662],{},", the receiver keeps working with the old secret until you deploy the new one, and with the new one as soon as you do. Nothing has to switch at the same moment; you have a whole day. After 24 hours only the new secret's signature is sent.",[1596,3972,3974],{"id":3973},"raw-body-pitfalls","Raw-body pitfalls",[1588,3976,3977,3978,3981],{},"The signature covers the ",[1592,3979,3980],{},"exact bytes"," DevStride sent. Parsing the JSON and serializing it again (even with no visible change) alters spacing, key order or escaping, and the signature no longer matches. This is the most common cause of a receiver that rejects every delivery.",[1679,3983,3984,3997],{},[1682,3985,3986],{},[1685,3987,3988,3991,3994],{},[1688,3989,3990],{},"Stack",[1688,3992,3993],{},"Do",[1688,3995,3996],{},"Avoid",[1698,3998,3999,4025,4041,4054,4076,4090,4105],{},[1685,4000,4001,4004,4016],{},[1703,4002,4003],{},"Express",[1703,4005,4006,4009,4010,4013,4014,2187],{},[1608,4007,4008],{},"express.raw({ type: 'application/json', limit: '1mb' })"," on the webhook route, then ",[1608,4011,4012],{},"req.body"," (a ",[1608,4015,2331],{},[1703,4017,4018,4021,4022],{},[1608,4019,4020],{},"express.json()"," before verifying, then ",[1608,4023,4024],{},"JSON.stringify(req.body)",[1685,4026,4027,4030,4035],{},[1703,4028,4029],{},"Next.js (App Router)",[1703,4031,4032],{},[1608,4033,4034],{},"Buffer.from(await request.arrayBuffer())",[1703,4036,4037,4040],{},[1608,4038,4039],{},"await request.json()"," before verifying",[1685,4042,4043,4046,4051],{},[1703,4044,4045],{},"Fastify",[1703,4047,4048,4049],{},"A content-type parser that keeps this route's body as a ",[1608,4050,2331],{},[1703,4052,4053],{},"Verifying the parsed object",[1685,4055,4056,4059,4068],{},[1703,4057,4058],{},"Flask / Django / FastAPI",[1703,4060,4061,4063,4064,4063,4066],{},[1608,4062,2823],{}," / ",[1608,4065,2827],{},[1608,4067,2831],{},[1703,4069,4070,4063,4073],{},[1608,4071,4072],{},"request.json",[1608,4074,4075],{},"json.dumps(...)",[1685,4077,4078,4081,4085],{},[1703,4079,4080],{},"Rails",[1703,4082,4083],{},[1608,4084,3356],{},[1703,4086,4087],{},[1608,4088,4089],{},"params.to_json",[1685,4091,4092,4095,4102],{},[1703,4093,4094],{},"ASP.NET Core",[1703,4096,4097,4098,4101],{},"Copy ",[1608,4099,4100],{},"Request.Body"," into a byte array first",[1703,4103,4104],{},"Binding to a model, then serializing it",[1685,4106,4107,4110,4120],{},[1703,4108,4109],{},"AWS Lambda behind API Gateway",[1703,4111,4112,4115,4116,4119],{},[1608,4113,4114],{},"event.body",", base64-decoded first when ",[1608,4117,4118],{},"event.isBase64Encoded"," is true",[1703,4121,4122],{},"Re-serializing the parsed body",[1588,4124,4125,4126,4129],{},"Also check that nothing in front of your code changes the body: some API gateways, WAFs and logging proxies re-encode JSON. And parse the body only ",[1592,4127,4128],{},"after"," verifying it.",[4131,4132,1824,4135,4137,4138,3584,4141,4144,4145,4148],"alert",{"title":4133,"type":4134},"Body size limits","warning",[1608,4136,1784],{}," event can carry up to 256 KB of comment HTML, so allow request bodies of at least 1 MB. Express's ",[1608,4139,4140],{},"raw",[1608,4142,4143],{},"json"," parsers default to 100 KB and answer larger events with ",[1608,4146,4147],{},"413",", which DevStride counts as a failed delivery and retries.",[1596,4150,4152],{"id":4151},"test-your-verifier","Test your verifier",[1588,4154,4155],{},"Run your function against this fixed example before connecting a real endpoint. These values are for testing only: no real endpoint uses this secret.",[1679,4157,4158,4168],{},[1682,4159,4160],{},[1685,4161,4162,4165],{},[1688,4163,4164],{},"Input",[1688,4166,4167],{},"Value",[1698,4169,4170,4180,4192,4200],{},[1685,4171,4172,4175],{},[1703,4173,4174],{},"Secret",[1703,4176,4177],{},[1608,4178,4179],{},"whsec_Zm9yLWRvY3Mtb25seS1uZXZlci1hLXJlYWwtc2VjcmV0",[1685,4181,4182,4188],{},[1703,4183,4184,4185,4187],{},"Timestamp (",[1608,4186,1621],{},", and your \"now\")",[1703,4189,4190],{},[1608,4191,1726],{},[1685,4193,4194,4197],{},[1703,4195,4196],{},"Body",[1703,4198,4199],{},"the JSON below, exactly these bytes: one line, no trailing newline",[1685,4201,4202,4207],{},[1703,4203,4204,4205],{},"Expected ",[1608,4206,1662],{},[1703,4208,4209],{},[1608,4210,4211],{},"a8bb7839b9aeb19af6b3195d9b3ba3d3231b107434fd44d48981035827bc32f4",[1843,4213,4216],{"className":4214,"code":4215,"language":4143,"meta":1849,"style":1849},"language-json shiki shiki-themes material-theme-lighter material-theme material-theme-palenight","{\"id\":\"2f6c1d0a-8b4e-5c3f-9a71-0e5d4c3b2a19\",\"data\":{\"comment\":{\"id\":\"c-1\"}},\"type\":\"comment.created\",\"origin\":{\"kind\":\"customer\",\"actorId\":\"req-1\",\"correlationId\":\"corr-1\"},\"apiVersion\":\"2026-10-16\",\"occurredAt\":\"2026-10-17T14:03:11.000Z\",\"organizationId\":\"1a2b3c4d-0000-4000-8000-000000000001\"}\n",[1608,4217,4218],{"__ignoreMap":1849},[1881,4219,4220,4223,4226,4228,4230,4232,4234,4236,4238,4240,4242,4245,4247,4250,4252,4255,4257,4259,4261,4263,4265,4267,4269,4272,4274,4277,4279,4281,4283,4285,4287,4289,4291,4293,4295,4298,4300,4302,4304,4307,4309,4311,4313,4316,4318,4320,4322,4325,4327,4329,4331,4334,4336,4338,4340,4343,4345,4347,4349,4352,4354,4357,4359,4362,4364,4366,4368,4371,4373,4375,4377,4380,4382,4384,4386,4389,4391,4393,4395,4398,4400,4402,4404,4407,4409],{"class":1883,"line":17},[1881,4221,4222],{"class":1890},"{",[1881,4224,4225],{"class":1890},"\"",[1881,4227,1750],{"class":1931},[1881,4229,4225],{"class":1890},[1881,4231,1948],{"class":1890},[1881,4233,4225],{"class":1890},[1881,4235,1744],{"class":1913},[1881,4237,4225],{"class":1890},[1881,4239,1898],{"class":1890},[1881,4241,4225],{"class":1890},[1881,4243,4244],{"class":1931},"data",[1881,4246,4225],{"class":1890},[1881,4248,4249],{"class":1890},":{",[1881,4251,4225],{"class":1890},[1881,4253,4254],{"class":1951},"comment",[1881,4256,4225],{"class":1890},[1881,4258,4249],{"class":1890},[1881,4260,4225],{"class":1890},[1881,4262,1750],{"class":2017},[1881,4264,4225],{"class":1890},[1881,4266,1948],{"class":1890},[1881,4268,4225],{"class":1890},[1881,4270,4271],{"class":1913},"c-1",[1881,4273,4225],{"class":1890},[1881,4275,4276],{"class":1890},"}},",[1881,4278,4225],{"class":1890},[1881,4280,1790],{"class":1931},[1881,4282,4225],{"class":1890},[1881,4284,1948],{"class":1890},[1881,4286,4225],{"class":1890},[1881,4288,1784],{"class":1913},[1881,4290,4225],{"class":1890},[1881,4292,1898],{"class":1890},[1881,4294,4225],{"class":1890},[1881,4296,4297],{"class":1931},"origin",[1881,4299,4225],{"class":1890},[1881,4301,4249],{"class":1890},[1881,4303,4225],{"class":1890},[1881,4305,4306],{"class":1951},"kind",[1881,4308,4225],{"class":1890},[1881,4310,1948],{"class":1890},[1881,4312,4225],{"class":1890},[1881,4314,4315],{"class":1913},"customer",[1881,4317,4225],{"class":1890},[1881,4319,1898],{"class":1890},[1881,4321,4225],{"class":1890},[1881,4323,4324],{"class":1951},"actorId",[1881,4326,4225],{"class":1890},[1881,4328,1948],{"class":1890},[1881,4330,4225],{"class":1890},[1881,4332,4333],{"class":1913},"req-1",[1881,4335,4225],{"class":1890},[1881,4337,1898],{"class":1890},[1881,4339,4225],{"class":1890},[1881,4341,4342],{"class":1951},"correlationId",[1881,4344,4225],{"class":1890},[1881,4346,1948],{"class":1890},[1881,4348,4225],{"class":1890},[1881,4350,4351],{"class":1913},"corr-1",[1881,4353,4225],{"class":1890},[1881,4355,4356],{"class":1890},"},",[1881,4358,4225],{"class":1890},[1881,4360,4361],{"class":1931},"apiVersion",[1881,4363,4225],{"class":1890},[1881,4365,1948],{"class":1890},[1881,4367,4225],{"class":1890},[1881,4369,4370],{"class":1913},"2026-10-16",[1881,4372,4225],{"class":1890},[1881,4374,1898],{"class":1890},[1881,4376,4225],{"class":1890},[1881,4378,4379],{"class":1931},"occurredAt",[1881,4381,4225],{"class":1890},[1881,4383,1948],{"class":1890},[1881,4385,4225],{"class":1890},[1881,4387,4388],{"class":1913},"2026-10-17T14:03:11.000Z",[1881,4390,4225],{"class":1890},[1881,4392,1898],{"class":1890},[1881,4394,4225],{"class":1890},[1881,4396,4397],{"class":1931},"organizationId",[1881,4399,4225],{"class":1890},[1881,4401,1948],{"class":1890},[1881,4403,4225],{"class":1890},[1881,4405,4406],{"class":1913},"1a2b3c4d-0000-4000-8000-000000000001",[1881,4408,4225],{"class":1890},[1881,4410,2610],{"class":1890},[3878,4412,4413,4426,4435,4448],{},[1616,4414,4415,4416,4419,4420,4422,4423,1656],{},"With the header ",[1608,4417,4418],{},"t=1760709791,v1=a8bb7839b9aeb19af6b3195d9b3ba3d3231b107434fd44d48981035827bc32f4"," and \"now\" set to ",[1608,4421,1726],{},", your function returns ",[1592,4424,4425],{},"true",[1616,4427,4428,4429,4432,4433,1656],{},"With \"now\" set to ",[1608,4430,4431],{},"1760710092"," (301 seconds later), it returns ",[1592,4434,2619],{},[1616,4436,4437,4438,4441,4442,4445,4446,1656],{},"With the body changed in any way (for example ",[1608,4439,4440],{},"\"customer\""," replaced by ",[1608,4443,4444],{},"\"member\"","), it returns ",[1592,4447,2619],{},[1616,4449,4450,4451,4454,4455,4457,4458,1656],{},"With the two-signature header of a rotation, ",[1608,4452,4453],{},"t=1760709791,v1=a8bb7839b9aeb19af6b3195d9b3ba3d3231b107434fd44d48981035827bc32f4,v1=be8e8ee0163bd2194be3b66a96e2749db980895d960d68296270de87b968c7bc",", it returns ",[1592,4456,4425],{}," both for the secret above and for ",[1608,4459,4460],{},"whsec_cHJldmlvdXMtc2VjcmV0LWZvci1kb2NzLW9ubHk",[4462,4463,4464],"style",{},"html pre.shiki code .s7zQu, html code.shiki .s7zQu{--shiki-light:#39ADB5;--shiki-light-font-style:italic;--shiki-default:#89DDFF;--shiki-default-font-style:italic;--shiki-dark:#89DDFF;--shiki-dark-font-style:italic}html pre.shiki code .sMK4o, html code.shiki .sMK4o{--shiki-light:#39ADB5;--shiki-default:#89DDFF;--shiki-dark:#89DDFF}html pre.shiki code .sTEyZ, html code.shiki .sTEyZ{--shiki-light:#90A4AE;--shiki-default:#EEFFFF;--shiki-dark:#BABED8}html pre.shiki code .sfazB, html code.shiki .sfazB{--shiki-light:#91B859;--shiki-default:#C3E88D;--shiki-dark:#C3E88D}html pre.shiki code .spNyl, html code.shiki .spNyl{--shiki-light:#9C3EDA;--shiki-default:#C792EA;--shiki-dark:#C792EA}html pre.shiki code .s2Zo4, html code.shiki .s2Zo4{--shiki-light:#6182B8;--shiki-default:#82AAFF;--shiki-dark:#82AAFF}html pre.shiki code .sHdIc, html code.shiki .sHdIc{--shiki-light:#90A4AE;--shiki-light-font-style:italic;--shiki-default:#EEFFFF;--shiki-default-font-style:italic;--shiki-dark:#BABED8;--shiki-dark-font-style:italic}html pre.shiki code .sBMFI, html code.shiki .sBMFI{--shiki-light:#E2931D;--shiki-default:#FFCB6B;--shiki-dark:#FFCB6B}html pre.shiki code .sbssI, html code.shiki .sbssI{--shiki-light:#F76D47;--shiki-default:#F78C6C;--shiki-dark:#F78C6C}html pre.shiki code .swJcz, html code.shiki .swJcz{--shiki-light:#E53935;--shiki-default:#F07178;--shiki-dark:#F07178}html pre.shiki code .sfNiH, html code.shiki .sfNiH{--shiki-light:#FF5370;--shiki-default:#FF9CAC;--shiki-dark:#FF9CAC}html pre.shiki code .sHwdD, html code.shiki .sHwdD{--shiki-light:#90A4AE;--shiki-light-font-style:italic;--shiki-default:#546E7A;--shiki-default-font-style:italic;--shiki-dark:#676E95;--shiki-dark-font-style:italic}html .light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html.light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}",{"title":1849,"searchDepth":244,"depth":244,"links":4466},[4467,4468,4469,4476,4477,4478,4479,4480],{"id":1598,"depth":244,"text":1599},{"id":1673,"depth":244,"text":1674},{"id":2626,"depth":244,"text":2627,"children":4470},[4471,4472,4473,4474,4475],{"id":2639,"depth":302,"text":2640},{"id":2838,"depth":302,"text":2839},{"id":3181,"depth":302,"text":3182},{"id":3372,"depth":302,"text":3373},{"id":3590,"depth":302,"text":3591},{"id":3872,"depth":244,"text":3873},{"id":3908,"depth":244,"text":3909},{"id":3939,"depth":244,"text":3940},{"id":3973,"depth":244,"text":3974},{"id":4151,"depth":244,"text":4152},"How DevStride signs every webhook with HMAC-SHA256, the exact string it signs, the headers it sends, replay protection, secret rotation and raw-body pitfalls, with tested verification code in Node.js, Python, Go, Ruby, PHP and C#.","md",null,{},{"title":1091,"description":4481},"pFPgXthFfkX_0QuTC7iYGEJgJRzy2eWTVjn_s5j6JDo",[4488,4490],{"title":1087,"path":1088,"stem":1089,"description":4489,"children":-1},"The JSON envelope every DevStride webhook carries and every field of item.created, item.status_changed, comment.created, sla.at_risk and sla.breached, with when each fires and a realistic example of each.",{"title":1095,"path":1096,"stem":1097,"description":4491,"children":-1},"What counts as a successful webhook delivery, the request time limits, the ten-attempt retry schedule over about 24 hours, per-endpoint ordering, at-least-once delivery, failing and recovered alerts, and how long the delivery log keeps each delivery.",1792296231815]