Initial json schema and tests
diff --git a/example/log.schema.json b/example/log.schema.json
new file mode 100644
index 0000000..e138c7f
--- /dev/null
+++ b/example/log.schema.json
@@ -0,0 +1,125 @@
+{
+    "$schema": "https://json-schema.org/draft/2020-12/schema",
+    "$id": "https://flagon.incubator.apache.org/log.schema.json",
+    "title": "Log",
+    "description": "A raw or custom log produced by userale",
+    "type": "object",
+    "properties": {
+        "target": {
+            "type": "string"
+        },
+        "path": {
+            "type": "array",
+            "items": {
+                "type": "string"
+            },
+            "minItems": 1
+        },
+        "pageUrl": {
+            "type": "string"
+        },
+        "pageTitle": {
+            "type": "string"
+        },
+        "browser": {
+            "type": "object",
+            "properties": {
+                "browser": {
+                    "type": "string"
+                },
+                "version": {
+                    "type": "string"
+                }
+            },
+            "required" : ["browser", "version"]
+        },
+        "clientTime": {
+            "type": "integer"
+        },
+        "microTime": {
+            "type": "number",
+            "minimum": 0,
+            "maxmaximumi": 1
+        },
+        "location": {
+          "type": "object",
+          "properties": {
+              "x": {
+                  "type": ["integer", "null"]
+              },
+              "y": {
+                  "type": ["integer", "null"]
+              }
+          },
+          "required" : ["x", "y"]
+        },
+        "scrnRes": {
+          "type": "object",
+          "properties": {
+              "height": {
+                  "type": "integer"
+              },
+              "width": {
+                  "type": "integer"
+              }
+          },
+          "required" : ["height", "width"]
+        },
+        "type": {
+          "type": "string"
+        },
+        "logType": {
+          "type": "string",
+          "enum": ["raw", "custom"]
+        },
+        "userAction": {
+          "type": "boolean"
+        },
+        "details": {
+          "type": "object"
+        },
+        "userId": {
+          "type": "string"
+        },
+        "toolVersion": {
+          "type": "string"
+        },
+        "toolName": {
+          "type": "string"
+        },
+        "useraleVersion": {
+          "type": "string"
+        },
+        "sessionID": {
+          "type": "string"
+        }
+    },
+    "required": [
+        "pageUrl",
+        "pageTitle",
+        "pageReferrer",
+        "browser",
+        "clientTime",
+        "scrnRes",
+        "logType",
+        "userAction",
+        "details",
+        "userId",
+        "toolVersion",
+        "toolName",
+        "useraleVersion",
+        "sessionID"
+    ],
+    "if": {
+        "properties": { "logType": { "const": "raw" } }
+    },
+    "then": {
+        "required": [
+            "target",
+            "path",
+            "microTime",
+            "location",
+            "type"
+        ]
+    }
+}
\ No newline at end of file
diff --git a/journey/userale.journey.cy.js b/journey/userale.journey.cy.js
index 1a73d39..52537a2 100644
--- a/journey/userale.journey.cy.js
+++ b/journey/userale.journey.cy.js
@@ -14,6 +14,8 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
+import { validate } from 'jsonschema';
+
 describe('Userale logging', () => {
     beforeEach(() => {
         cy.intercept('POST', 'http://localhost:8000/').as('backend')
@@ -62,4 +64,24 @@
             expect(actualValue).to.equal(expectedValue)
         })
     });
+
+    it('produces valid logs', () => {
+        cy.visit('http://localhost:8000');
+        cy.wait('@backend').then(xhr => {
+            var schema = require('../example/log.schema.json');
+            for(const log of xhr.request.body) {
+                const result = validate(log, schema);
+                expect(result.valid, result.errors).to.equal(true);
+            }
+        })
+        cy.contains(/click me/i).click();
+        cy.wait('@backend').then(xhr => {
+            var schema = require('../example/log.schema.json');
+            for(const log of xhr.request.body) {
+                const result = validate(log, schema);
+                expect(result.valid, result.errors).to.equal(true);
+            }
+        })
+    });
+
 });
diff --git a/package-lock.json b/package-lock.json
index f728a5e..0ddfb2a 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -29,6 +29,7 @@
         "express": "^4.18.2",
         "global-jsdom": "^9.1.0",
         "jsdom": "^22.1.0",
+        "jsonschema": "^1.4.1",
         "mocha": "^10.2.0",
         "nodemon": "^3.0.2",
         "rollup": "^4.6.1",
@@ -5567,6 +5568,15 @@
         "graceful-fs": "^4.1.6"
       }
     },
+    "node_modules/jsonschema": {
+      "version": "1.4.1",
+      "resolved": "https://registry.npmjs.org/jsonschema/-/jsonschema-1.4.1.tgz",
+      "integrity": "sha512-S6cATIPVv1z0IlxdN+zUk5EPjkGCdnhN4wVSBlvoUO1tOLJootbo9CquNJmbIh4yikWHiUedhRYrNPn1arpEmQ==",
+      "dev": true,
+      "engines": {
+        "node": "*"
+      }
+    },
     "node_modules/jsprim": {
       "version": "2.0.2",
       "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-2.0.2.tgz",
diff --git a/package.json b/package.json
index 7da9de1..4fb4893 100644
--- a/package.json
+++ b/package.json
@@ -69,6 +69,7 @@
     "express": "^4.18.2",
     "global-jsdom": "^9.1.0",
     "jsdom": "^22.1.0",
+    "jsonschema": "^1.4.1",
     "mocha": "^10.2.0",
     "nodemon": "^3.0.2",
     "rollup": "^4.6.1",