diff --git a/backend/Procfile b/backend/Procfile
index 225d08825762c0096045aacf83d2beb22cced783..f64d6690be5d9fa0cea57c455f78fb4e1bec2d1f 100644
--- a/backend/Procfile
+++ b/backend/Procfile
@@ -1 +1 @@
-web: deno run --allow-net src/index.ts --port=${PORT}
+web: deno run --allow-write --allow-read --allow-net src/index.ts --port=${PORT}
diff --git a/backend/src/image.ts b/backend/src/image.ts
new file mode 100644
index 0000000000000000000000000000000000000000..04f36fd5c3733aa01c1b66bf150aaec88169383d
--- /dev/null
+++ b/backend/src/image.ts
@@ -0,0 +1,55 @@
+import {
+  readAll,
+  readerFromStreamReader,
+} from "https://deno.land/std@0.95.0/io/mod.ts";
+import { decode, Image } from "https://deno.land/x/jpegts@1.1/mod.ts";
+import { resize } from "https://deno.land/x/deno_image@v0.0.3/mod.ts";
+
+export const compareMunicipalRibbon = async (
+  ribbonUrl: string,
+  imageToCompareTo: Image,
+): Promise<number> => {
+  const ribbon = (await fetch(ribbonUrl))?.body?.getReader();
+  if (ribbon) {
+    const reader = readerFromStreamReader(ribbon);
+    const rawImage = await readAll(reader);
+    const picture = decode(
+      await resize(rawImage, {
+        height: imageToCompareTo.height,
+        width: imageToCompareTo.width,
+        aspectRatio: false,
+      }),
+    );
+    return compareImages(picture, imageToCompareTo);
+  }
+
+  throw new Error("Couldn't fetch remote Image!");
+};
+
+const compareImages = (
+  firstImage: Image,
+  secondImage: Image,
+): number => {
+  if (
+    firstImage.width != secondImage.width ||
+    firstImage.height != secondImage.height
+  ) {
+    throw new Error(
+      "Can't compare images of different sizes. " + firstImage.width + "x" +
+        firstImage.height + " vs " + secondImage.width + "x" +
+        secondImage.height,
+    );
+  }
+
+  let diff = 0;
+  for (let i = 0; i < firstImage.data.length / 5; i++) {
+    diff += Math.abs(firstImage.data[4 * i + 0] - secondImage.data[4 * i + 0]) /
+      255;
+    diff += Math.abs(firstImage.data[4 * i + 1] - secondImage.data[4 * i + 1]) /
+      255;
+    diff += Math.abs(firstImage.data[4 * i + 2] - secondImage.data[4 * i + 2]) /
+      255;
+  }
+
+  return 100 - 100 * diff / (firstImage.width * firstImage.height * 3);
+};
diff --git a/backend/src/index.ts b/backend/src/index.ts
index cf0c1156f71a2e0b9f0a06058f157d933a069a50..4d73fdeada90878be9386bf105a6457bb0394e59 100644
--- a/backend/src/index.ts
+++ b/backend/src/index.ts
@@ -1,7 +1,8 @@
 import { Application, Router } from "https://deno.land/x/oak@v7.3.0/mod.ts";
 import { parse } from "https://deno.land/std@0.95.0/flags/mod.ts";
+import logger from "https://deno.land/x/oak_logger@1.0.0/mod.ts";
 
-import { getAllRibbons, getRibbon, Ribbon } from "./ribbon.ts";
+import { compareRibbon, getAllRibbons, getRibbon, Ribbon } from "./ribbon.ts";
 
 const { args } = Deno;
 const DEFAULT_PORT = 8000;
@@ -14,10 +15,17 @@ const state: Ribbon[] = await (await fetch(
 
 const app = new Application({ state });
 
+app.use(logger.logger);
+app.use(logger.responseTime);
+
 const router = new Router();
 
 router.get("/ribbons", getAllRibbons);
 router.get("/ribbons/:municipality", getRibbon);
+router.post(
+  "/ribbons/compare/:municipality",
+  compareRibbon,
+);
 
 app.use(router.routes());
 
diff --git a/backend/src/ribbon.ts b/backend/src/ribbon.ts
index 33f29962f3b117ef6df3e97433be61b443985b40..c0e175cebe2dd8911fe6cb438a6b6583e08cc371 100644
--- a/backend/src/ribbon.ts
+++ b/backend/src/ribbon.ts
@@ -1,4 +1,6 @@
 import { helpers, RouterContext } from "https://deno.land/x/oak@v7.3.0/mod.ts";
+import { compareMunicipalRibbon } from "./image.ts";
+import { decode, Image } from "https://deno.land/x/jpegts@1.1/mod.ts";
 
 export interface Ribbon {
   acceptance: string;
@@ -42,6 +44,7 @@ export const getAllRibbons = (ctx: RouterContext) => {
 export const getRibbon = ({ params, response, state }: RouterContext) => {
   if (params?.municipality === undefined) {
     response.status = 400;
+    return;
   }
   const municipality = params?.municipality;
   const ribbon = state.find(
@@ -56,3 +59,59 @@ export const getRibbon = ({ params, response, state }: RouterContext) => {
   response.status = 200;
   response.body = ribbon;
 };
+
+export const compareRibbon = async (
+  { params, request, response, state }: RouterContext,
+) => {
+  if (params?.municipality === undefined) {
+    response.status = 400;
+    return;
+  }
+  const municipality = params?.municipality;
+  const ribbon: Ribbon = state.find(
+    (ribbon: Ribbon) => ribbon.municipality.endsWith(`/${municipality}`),
+  );
+
+  if (ribbon === undefined) {
+    response.status = 404;
+    response.body = "Couldn't find municipality " + municipality;
+    return;
+  }
+
+  const { value } = request.body({ type: "form-data" });
+  const { files } = await value.read();
+  if (!files) {
+    response.status = 400;
+    return;
+  }
+  const filename = files[0].filename;
+  if (!filename) {
+    response.status = 400;
+    return;
+  }
+
+  let img: Image | undefined = undefined;
+  try {
+    img = decode(
+      await Deno.readFile(filename),
+    );
+  } catch (e) {
+    response.status = 500;
+    response.body = "" + e;
+    return;
+  }
+
+  let matchPercentage = 0;
+  try {
+    matchPercentage = await compareMunicipalRibbon(
+      ribbon.img,
+      img,
+    );
+  } catch (e) {
+    response.status = 500;
+    response.body = "" + e;
+    return;
+  }
+
+  response.body = { matchPercentage: matchPercentage };
+};