@@ -9,8 +9,10 @@ occur on AMO. These webhooks are registed in the AMO (django) admin.
99
1010When a [ scanner webhook event] ( #scanner-webhook-events ) occurs, AMO will send an
1111HTTP request to each webhook subscribed to this event. The payload sent to the
12- webook depends on the event. The response from each webhook will lead to the
13- creation of a [ scanner result] ( #scanner-results ) .
12+ webook depends on the event. AMO creates a [ scanner result] ( #scanner-results )
13+ before calling the webhook, and includes a ` scanner_result_url ` in the payload
14+ that allows the scanner to [ asynchronously send its results] ( #asynchronous-scanning )
15+ back to AMO.
1416
1517Each service registered as a scanner webhook must be protected with a shared
1618secret (api) key. Read [ the scanners authentication
@@ -48,11 +50,13 @@ validation chain.
4850
4951The payload sent looks like this. Assuming correct permissions, the URL in
5052` download_url ` allows the services notified for this event to download the (raw)
51- uploaded file.
53+ uploaded file. The ` scanner_result_url ` allows the scanner to send results
54+ asynchronously.
5255
5356``` json
5457{
55- "download_url" : " http://olympia.test/uploads/file/42"
58+ "download_url" : " http://olympia.test/uploads/file/42" ,
59+ "scanner_result_url" : " http://olympia.test/api/v5/scanner/results/123/"
5660}
5761```
5862
@@ -68,7 +72,8 @@ The payload sent looks like this:
6872 "version_id" : 42 ,
6973 "download_source_url" : " http://olympia.test/downloads/source/42" ,
7074 "license_slug" : " MPL-2.0" ,
71- "activity_log_id" : 2170
75+ "activity_log_id" : 2170 ,
76+ "scanner_result_url" : " http://olympia.test/api/v5/scanner/results/124/"
7277}
7378```
7479
@@ -112,6 +117,8 @@ These actions are defined in `src/olympia/scanners/actions.py`.
112117(scanners-authentication)=
113118### Authentication
114119
120+ #### Authenticating incoming webhook calls
121+
115122Scanners must verify the incoming requests using the ` Authorization ` header and
116123not allow unauthenticated requests. For every webhook call, AMO will send this
117124header using the _ API key_ defined in the Django admin as follows:
@@ -124,11 +131,57 @@ Authorization: HMAC-SHA256 <hexdigest>
124131the _ API key_ used as the secret key. Make sure to hash the _ raw_ request's
125132body.
126133
134+ #### Authenticating asynchronous result submissions
135+
136+ When sending results asynchronously via PATCH to the ` scanner_result_url ` ,
137+ scanners must authenticate using JWT credentials. Each scanner webhook has an
138+ automatically created service account, and the JWT keys for this account are
139+ displayed in the Django admin after creating the webhook.
140+
141+ Use the JWT key and secret to generate a JWT token and include it in the
142+ ` Authorization ` header when making PATCH requests to submit results
143+ asynchronously.
144+
127145### API response
128146
129- Scanners must return a JSON response that contains the following fields:
147+ Scanners can choose to return results synchronously or asynchronously:
148+
149+ #### Synchronous response
150+
151+ Scanners can return a JSON response immediately that contains the following fields:
130152
131153- ` version ` : the scanner version
154+ - ` matchedRules ` : an array of matched rule identifiers (string)
155+
156+ (asynchronous-scanning)=
157+ #### Asynchronous response
158+
159+ Scanners can also return a quick acknowledgment response (or any response) and
160+ send their results later using the ` scanner_result_url ` provided in the webhook
161+ payload. This is useful for long-running scans.
162+
163+ To send results asynchronously:
164+
165+ 1 . The scanner receives a webhook call with a ` scanner_result_url ` in the payload
166+ 2 . The scanner returns a quick response (e.g., HTTP 202 Accepted)
167+ 3 . The scanner performs its analysis
168+ 4 . The scanner sends a PATCH request to the ` scanner_result_url ` with the results
169+
170+ The PATCH request must be authenticated using the [ service account JWT
171+ credentials] ( #scanners-authentication ) and include a JSON body with a ` results `
172+ field:
173+
174+ ``` json
175+ {
176+ "results" : {
177+ "version" : " 1.0.0" ,
178+ "matchedRules" : []
179+ }
180+ }
181+ ```
182+
183+ The ` results ` field should contain the same data structure as a synchronous
184+ response would return.
132185
133186### Creating a new scanner
134187
@@ -149,7 +202,13 @@ import { createExpressApp } from "addons-scanner-utils";
149202const handler = (req , res ) => {
150203 console .log ({ data: req .body });
151204
205+ // Option 1: Synchronous response
152206 res .json ({ version: " 1.0.0" });
207+
208+ // Option 2: Asynchronous response (for long-running scans)
209+ // res.status(202).json({ message: "Scan started" });
210+ // // Perform scanning asynchronously and later send results to:
211+ // // req.body.scanner_result_url
153212};
154213
155214const app = createExpressApp ({
@@ -183,7 +242,8 @@ When uploading a new file, you should see the following in the console:
183242``` js
184243{
185244 data: {
186- download_url: " http://olympia.test/uploads/file/fa7868396b7e44ef8a0711f608f534f7/?access_token=w0Tl7qmJqBMQ4gtitKbcdKozulWVQWhkU0wEA10N"
245+ download_url: " http://olympia.test/uploads/file/fa7868396b7e44ef8a0711f608f534f7/?access_token=w0Tl7qmJqBMQ4gtitKbcdKozulWVQWhkU0wEA10N" ,
246+ scanner_result_url: " http://olympia.test/api/v5/scanner/results/123/"
187247 }
188248}
189249```
0 commit comments