diff --git a/README.md b/README.md
index d99acbb5a298350385d3e58efd4479b52ff695df..a1703281926b7f27b4613762d5c2955edc11e764 100644
--- a/README.md
+++ b/README.md
@@ -85,11 +85,27 @@ where
 * `ENTITY_ID_HERE` is the entity ID of the SP
 * `BEARER_TOKEN_HERE` is your authorization token for the conformance IdP
 
+### SP-initiated (workflow)
+
+Peforming SP-initiated login is more difficult than the IdP-initiated login used in previous templates.
+
+You can use the included [nuclei workflow](https://docs.projectdiscovery.io/templates/workflows/overview)
+to scan a server for available endpoints and perform SP-initiated login if possible:
+
+```sh
+nuclei -w workflow/saml-sp-workflow.yaml -u https://sp.example.com/ -code -sf secret-file.yaml
+```
+
+where
+
+* `https://sp.example.com/` is the address of a (potential) SP server (only the domain is relevant)
+
 ## Current limitations
 
 * templates are only usable with the conformace IdP
   * testing cannot be run in parallel for the same entity ID
 * only `HTTP-POST` binding is currently supported
-* nuclei has to be invoked individually for each target (for each entity ID)
-* only SPs which accept unsolicited logins (IdP-initiated) can be tested
+* IdP-initiated templates have to be invoked individually for each target (for each entity ID)
+* only SPs which accept unsolicited logins (IdP-initiated)
+  or with a [Request Initiation](https://docs.oasis-open.org/security/saml/Post2.0/sstc-request-initiation.html) endpoint can be tested
 * headless browser test behaves differently than raw HTTP test (nuclei limitation)
diff --git a/nuclei-templates/saml-raw-all.yaml b/nuclei-templates/saml-raw-all.yaml
index 0fc049f842956c288c38e73cb5d09afd2b8dfdf5..3ad63d9ebcad7e1f2b7f8ccd6a5d45c03b3892d2 100644
--- a/nuclei-templates/saml-raw-all.yaml
+++ b/nuclei-templates/saml-raw-all.yaml
@@ -3,7 +3,7 @@ info:
   name: SAML signature validation
   author: T&I Incubator, GÉANT
   severity: high
-  tags: saml,raw
+  tags: saml,signature,raw
 variables:
   CONFORMANCE_IDP_BASE_URL: https://conformance-idp.maiv1.incubator.geant.org/
   CONFORMANCE_IDP_HOSTNAME: '{{replace_regex(CONFORMANCE_IDP_BASE_URL, "^https?://|/.*$", "")}}'
diff --git a/workflow/saml-detect-endpoints.yaml b/workflow/saml-detect-endpoints.yaml
new file mode 100644
index 0000000000000000000000000000000000000000..aeb52c221b68af46d8feba53266563c59002036a
--- /dev/null
+++ b/workflow/saml-detect-endpoints.yaml
@@ -0,0 +1,107 @@
+id: saml-detect-endpoints
+info:
+  name: SAML endpoints detection
+  author: T&I Incubator, GÉANT
+  severity: info
+  tags: saml,detect,tech
+
+variables:
+  CONFORMANCE_IDP_ENTITY_ID: "urn:x-simplesamlphp:geant:incubator:conformance-idp"
+
+http:
+  - method: GET
+    path:
+      - "{{RootURL}}{{spinitendpoint}}?entityID={{url_encode(CONFORMANCE_IDP_ENTITY_ID)}}"
+    redirects: false
+    attack: batteringram
+    payloads:
+      spinitendpoint:
+        - ""
+        - "/"
+        - "/api/login/sso"
+        - "/api/saml-client/returnFromSAML"
+        - "/applications/secure-sp/Shibboleth.sso/Login"
+        - "/auth/shibboleth.sso/ds"
+        - "/auth/shibexternal/Shibboleth.sso/DS"
+        - "/auth/shibexternal/Shibboleth.sso/Login"
+        - "/edugainlogin/Shibboleth.sso/Login"
+        - "/edugain/Shibboleth.sso/Login"
+        - "/eduid/Shibboleth.sso/Login"
+        - "/federation-manager/Shibboleth.sso/Login"
+        - "/login/shibboleth/callback"
+        - "/research-and-scholarship/Shibboleth.sso/DS"
+        - "/research-and-scholarship/Shibboleth.sso/EDS"
+        - "/research-and-scholarship/Shibboleth.sso/Login"
+        - "/research-and-scholarship/Shibboleth.sso/Login1"
+        - "/research-and-scholarship/Shibboleth.sso/SeamlessAccess"
+        - "/research-and-scholarship/Shibboleth.sso/Wayfinder"
+        - "/saml/login"
+        - "/saml/sp"
+        - "/saml/start"
+        - "/Shibboleth.sso/discovery"
+        - "/Shibboleth.sso/Discovery"
+        - "/shibboleth.sso/DS"
+        - "/Shibboleth.sso/DS"
+        - "/Shibboleth.sso/DS/Login"
+        - "/Shibboleth.sso/DS/seamless-access"
+        - "/Shibboleth.sso/DS/thiss.io"
+        - "/Shibboleth.sso/DS-WayfOA"
+        - "/Shibboleth.sso/DS1"
+        - "/Shibboleth.sso/DS2"
+        - "/Shibboleth.sso/DS3"
+        - "/Shibboleth.sso/EDS"
+        - "/Shibboleth.sso/edugain"
+        - "/Shibboleth.sso/FORCE"
+        - "/-shibboleth.sso/Login"
+        - "/shibboleth.sso/Login"
+        - "/Shibboleth.sso/Login"
+        - "/Shibboleth.sso/Loginn"
+        - "/Shibboleth.sso/Login1"
+        - "/Shibboleth.sso/OpenAthens"
+        - "/Shibboleth.sso/SeamlessAccess"
+        - "/Shibboleth.sso/SeamlessLogin"
+        - "/Shibboleth.sso/Shibboleth.sso/Login"
+        - "/Shibboleth.sso/shibtest"
+        - "/Shibboleth.sso/UniboAuth"
+        - "/Shibboleth.sso/WAYF"
+        - "/Shibboleth.sso/WAYF/Chooser"
+        - "/test/edugain/Shibboleth.sso/Login"
+        - "/wayf/"
+        - "/wayfless/boa"
+    stop-at-first-match: true
+    matchers:
+      - type: dsl
+        name: hasspinitendpoint
+        condition: and
+        dsl:
+          - "status_code == 302 || status_code == 303 || status_code == 307"
+          - "contains(location, 'SAMLRequest=')"
+    extractors:
+      - type: dsl
+        name: spinitendpoint
+        dsl:
+          - "spinitendpoint"
+
+code:
+  - engine:
+      - python3
+      - python
+      - py
+    source: |
+      import base64
+      import zlib
+      import os
+      import re
+      from urllib.parse import unquote
+
+      saml_request = unquote(re.sub(r'^.*SAMLRequest=(.*?)(&|$).*', r'\1', os.getenv("http_location")))
+      saml_request = zlib.decompress(base64.b64decode(saml_request), -zlib.MAX_WBITS).decode()
+      SP_ENTITY_ID = re.sub(r'^.*>(.*?)</\w+:Issuer.*$', r'\1', saml_request)
+      print(SP_ENTITY_ID)
+
+    extractors:
+      - type: dsl
+        name: SP_ENTITY_ID
+        dsl:
+          - response
+# digest: 4a0a0047304502205ecec927cfdf45f5d2711393ed7e8321517f98a969858659a1aa75db26d0b23b022100a390a5522fef78825412f4b60c38abb0f7f41d6f5ac970b24bb5b4c0308887ad:16ea744faf4d9956cf0107f1f3cb7c4c
\ No newline at end of file
diff --git a/workflow/saml-signature-request-init-raw.yaml b/workflow/saml-signature-request-init-raw.yaml
new file mode 100644
index 0000000000000000000000000000000000000000..6215f0f55e167416a9509e82a3fe6aeb5a07b431
--- /dev/null
+++ b/workflow/saml-signature-request-init-raw.yaml
@@ -0,0 +1,82 @@
+id: saml-signature-request-init-raw
+info:
+  name: SP initiated SAML authentication endpoint check
+  author: T&I Incubator, GÉANT
+  severity: high
+  tags: saml,signature,raw
+variables:
+  CONFORMANCE_IDP_BASE_URL: https://conformance-idp.maiv1.incubator.geant.org/
+  CONFORMANCE_IDP_HOSTNAME: '{{replace_regex(CONFORMANCE_IDP_BASE_URL, "^https?://|/.*$", "")}}'
+  CONFORMANCE_IDP_ENTITY_ID: "urn:x-simplesamlphp:geant:incubator:conformance-idp"
+  TEST_CASES:
+    - noSignature
+    - invalidSignature
+
+flow: |
+  set("TEST_CASE", "standardResponse");
+  http();
+  for (let testcase of iterate(template["TEST_CASES"])) {
+    set("TEST_CASE", testcase);
+    http();
+  }
+
+http:
+  - raw:
+      - |
+        @Host: https://{{CONFORMANCE_IDP_HOSTNAME}}
+        POST /module.php/conformance/test/setup?testId={{url_encode(TEST_CASE)}}&spEntityId={{url_encode(SP_ENTITY_ID)}} HTTP/1.1
+        Host: {{CONFORMANCE_IDP_HOSTNAME}}
+
+      - |
+        GET {{spinitendpoint}}?entityID={{url_encode(CONFORMANCE_IDP_ENTITY_ID)}} HTTP/1.1
+        Host: {{Hostname}}
+
+      # HTTP-POST binding
+      - |
+        @Host: {{replace_regex(trim(acs,"[]"), "^https?://|/.*$", "")}}
+        POST {{trim(acs,"[]")}} HTTP/1.1
+        Host: {{replace_regex(trim(acs,"[]"), "^https?://|/.*$", "")}}
+        Content-Type: application/x-www-form-urlencoded
+
+        SAMLResponse={{url_encode(trim(samlresponse,"[]"))}}
+
+    disable-path-automerge: true
+    redirects: true
+    extractors:
+      - type: xpath
+        name: acs
+        internal: true
+        attribute: action
+        xpath:
+          - '/html/body/form'
+      - type: xpath
+        name: samlresponse
+        internal: true
+        attribute: value
+        xpath:
+          - '/html/body/form/input[2]'
+      # breaks the template :(
+      #- type: dsl
+      #  dsl:
+      #    - 'TEST_CASE'
+    matchers-condition: and
+    matchers:
+      - type: status
+        condition: or
+        status:
+          - 200
+          - 302
+          - 303
+      - type: word
+        words:
+          - Welcome
+          - Hello
+          - Logged in
+        condition: or
+        part: body
+      - type: dsl
+        dsl:
+          # ignore the happy case
+          - '!contains(TEST_CASE, "standardResponse")'
+        condition: and
+
diff --git a/workflow/saml-sp-workflow.yaml b/workflow/saml-sp-workflow.yaml
new file mode 100644
index 0000000000000000000000000000000000000000..8d8d07950ed609bb7c5b0455c4a680bd7793e98e
--- /dev/null
+++ b/workflow/saml-sp-workflow.yaml
@@ -0,0 +1,19 @@
+id: saml-sp-workflow
+info:
+  name: SAML signature checks
+  author: T&I Incubator
+  description: Scan SAML SPs for SAML signature validation problems and vulnerabilities
+  severity: high
+workflows:
+  - template: workflow/saml-detect-endpoints.yaml
+    subtemplates:
+      - template: workflow/saml-signature-request-init-raw.yaml
+    # unfortunatelly it seems that is not possible to combine matchers and shared execution context like this:
+    #matchers:
+      #- name: hasspinitendpoint
+        #subtemplates:
+          #- template: nuclei-templates/saml-signature-request-init-raw.yaml
+            #subtemplates:
+            #  - template: nuclei-templates/saml-signature-request-init-invalid-signatures.yaml
+          #- tags: ssl,tls,dns,tech,detect
+  - tags: ssl,tls,dns,tech,detect