diff --git a/config/default.json b/config/default.json
index e642583f7..c2b0f0e2f 100644
--- a/config/default.json
+++ b/config/default.json
@@ -5,6 +5,7 @@
   "contentProxyUrl": "random.snode",
   "localServerPort": "8081",
   "snodeServerPort": "8080",
+  "defaultPoWDifficulty": "100",
   "disableAutoUpdate": false,
   "updatesUrl": "https://updates2.signal.org/desktop",
   "updatesPublicKey":
diff --git a/js/background.js b/js/background.js
index 76ae88606..6bdb08222 100644
--- a/js/background.js
+++ b/js/background.js
@@ -233,6 +233,11 @@
       window.libloki.api.sendOnlineBroadcastMessage(pubKey, isPing);
     });
 
+    const currentPoWDifficulty = storage.get('PoWDifficulty', null);
+    if (!currentPoWDifficulty) {
+      storage.put('PoWDifficulty', window.getDefaultPoWDifficulty());
+    }
+
     // These make key operations available to IPC handlers created in preload.js
     window.Events = {
       getDeviceName: () => textsecure.storage.user.getDeviceName(),
diff --git a/js/modules/loki_message_api.js b/js/modules/loki_message_api.js
index f24afb171..75de95d4b 100644
--- a/js/modules/loki_message_api.js
+++ b/js/modules/loki_message_api.js
@@ -31,10 +31,10 @@ const filterIncomingMessages = async messages => {
 };
 
 const calcNonce = (messageEventData, pubKey, data64, timestamp, ttl) => {
+  const difficulty = window.storage.get('PoWDifficulty', null);
   // Nonce is returned as a base64 string to include in header
   window.Whisper.events.trigger('calculatingPoW', messageEventData);
-  const development = window.getEnvironment() !== 'production';
-  return callWorker('calcPoW', timestamp, ttl, pubKey, data64, development);
+  return callWorker('calcPoW', timestamp, ttl, pubKey, data64, difficulty);
 };
 
 const trySendP2p = async (pubKey, data64, isPing, messageEventData) => {
@@ -124,7 +124,17 @@ class LokiMessageAPI {
       promises.push(this.openSendConnection(params));
     }
 
-    const results = await Promise.all(promises);
+    let results;
+    try {
+      results = await Promise.all(promises);
+    } catch (e) {
+      if (e instanceof textsecure.WrongDifficultyError) {
+        // Force nonce recalculation
+        this.sendMessage(pubKey, data, messageTimeStamp, ttl, options);
+        return;
+      }
+      throw e;
+    }
     delete this.sendingSwarmNodes[timestamp];
     if (results.every(value => value === false)) {
       throw new window.textsecure.EmptySwarmError(
@@ -155,7 +165,19 @@ class LokiMessageAPI {
     while (successiveFailures < 3) {
       await sleepFor(successiveFailures * 500);
       try {
-        await rpc(`https://${url}`, this.snodeServerPort, 'store', params);
+        const result = await rpc(
+          `https://${url}`,
+          this.snodeServerPort,
+          'store',
+          params
+        );
+
+        // Make sure we aren't doing too much PoW
+        const currentDifficulty = window.storage.get('PoWDifficulty', null);
+        const newDifficulty = result.difficulty;
+        if (newDifficulty != null && newDifficulty !== currentDifficulty) {
+          window.storage.put('PoWDifficulty', newDifficulty);
+        }
         return true;
       } catch (e) {
         log.warn('Loki send message:', e);
@@ -164,6 +186,12 @@ class LokiMessageAPI {
           await lokiSnodeAPI.updateSwarmNodes(params.pubKey, newSwarm);
           this.sendingSwarmNodes[params.timestamp] = newSwarm;
           return false;
+        } else if (e instanceof textsecure.WrongDifficultyError) {
+          const { newDifficulty } = e;
+          if (!Number.isNaN(newDifficulty)) {
+            window.storage.put('PoWDifficulty', newDifficulty);
+          }
+          throw e;
         } else if (e instanceof textsecure.NotFoundError) {
           // TODO: Handle resolution error
           successiveFailures += 1;
diff --git a/js/modules/loki_rpc.js b/js/modules/loki_rpc.js
index a61422506..1e0f52975 100644
--- a/js/modules/loki_rpc.js
+++ b/js/modules/loki_rpc.js
@@ -6,6 +6,21 @@ const { parse } = require('url');
 const LOKI_EPHEMKEY_HEADER = 'X-Loki-EphemKey';
 const endpointBase = '/v1/storage_rpc';
 
+const decryptResponse = async (response, address) => {
+  try {
+    const ciphertext = await response.text();
+    const plaintext = await libloki.crypto.snodeCipher.decrypt(
+      address,
+      ciphertext
+    );
+    const result = plaintext === '' ? {} : JSON.parse(plaintext);
+    return result;
+  } catch (e) {
+    log.warn(`Could not decrypt response from ${address}`, e);
+  }
+  return {};
+};
+
 // A small wrapper around node-fetch which deserializes response
 const fetch = async (url, options = {}) => {
   const timeout = options.timeout || 10000;
@@ -39,49 +54,41 @@ const fetch = async (url, options = {}) => {
       method,
     });
 
+    let result;
+    // Wrong swarm
     if (response.status === 421) {
-      let newSwarm = await response.text();
       if (doEncryptChannel) {
-        try {
-          newSwarm = await libloki.crypto.snodeCipher.decrypt(
-            address,
-            newSwarm
-          );
-        } catch (e) {
-          log.warn(`Could not decrypt response from ${address}`, e);
-        }
-        try {
-          newSwarm = newSwarm === '' ? {} : JSON.parse(newSwarm);
-        } catch (e) {
-          log.warn(`Could not parse string to json ${newSwarm}`, e);
-        }
+        result = decryptResponse(response, address);
+      } else {
+        result = await response.json();
       }
+      const newSwarm = result.snodes ? result.snodes : [];
       throw new textsecure.WrongSwarmError(newSwarm);
     }
 
+    // Wrong PoW difficulty
+    if (response.status === 432) {
+      if (doEncryptChannel) {
+        result = decryptResponse(response, address);
+      } else {
+        result = await response.json();
+      }
+      const { difficulty } = result;
+      throw new textsecure.WrongDifficultyError(difficulty);
+    }
+
     if (!response.ok) {
       throw new textsecure.HTTPError('Loki_rpc error', response);
     }
 
-    let result;
     if (response.headers.get('Content-Type') === 'application/json') {
       result = await response.json();
     } else if (options.responseType === 'arraybuffer') {
       result = await response.buffer();
+    } else if (doEncryptChannel) {
+      result = decryptResponse(response, address);
     } else {
       result = await response.text();
-      if (doEncryptChannel) {
-        try {
-          result = await libloki.crypto.snodeCipher.decrypt(address, result);
-        } catch (e) {
-          log.warn(`Could not decrypt response from ${address}`, e);
-        }
-        try {
-          result = result === '' ? {} : JSON.parse(result);
-        } catch (e) {
-          log.warn(`Could not parse string to json ${result}`, e);
-        }
-      }
     }
 
     return result;
diff --git a/js/util_worker_tasks.js b/js/util_worker_tasks.js
index 446ee7da8..b084e7b1a 100644
--- a/js/util_worker_tasks.js
+++ b/js/util_worker_tasks.js
@@ -47,7 +47,7 @@ function calcPoW(
   pubKey,
   data,
   development,
-  nonceTrials = undefined,
+  difficulty = undefined,
   increment = 1,
   startNonce = 0
 ) {
@@ -57,7 +57,7 @@ function calcPoW(
     pubKey,
     data,
     development,
-    nonceTrials,
+    difficulty,
     increment,
     startNonce
   );
diff --git a/libloki/proof-of-work.js b/libloki/proof-of-work.js
index 039e2395d..b1359d4e5 100644
--- a/libloki/proof-of-work.js
+++ b/libloki/proof-of-work.js
@@ -1,8 +1,7 @@
 /* global dcodeIO, crypto, JSBI */
 const NONCE_LEN = 8;
 // Modify this value for difficulty scaling
-const DEV_NONCE_TRIALS = 10;
-const PROD_NONCE_TRIALS = 100;
+const FALLBACK_DIFFICULTY = 10;
 
 const pow = {
   // Increment Uint8Array nonce by '_increment' with carrying
@@ -62,8 +61,7 @@ const pow = {
     ttl,
     pubKey,
     data,
-    development = false,
-    _nonceTrials = null,
+    _difficulty = null,
     increment = 1,
     startNonce = 0
   ) {
@@ -74,9 +72,8 @@ const pow = {
       ).toArrayBuffer()
     );
 
-    const nonceTrials =
-      _nonceTrials || (development ? DEV_NONCE_TRIALS : PROD_NONCE_TRIALS);
-    const target = pow.calcTarget(ttl, payload.length, nonceTrials);
+    const difficulty = _difficulty || FALLBACK_DIFFICULTY;
+    const target = pow.calcTarget(ttl, payload.length, difficulty);
 
     let nonce = new Uint8Array(NONCE_LEN);
     nonce = pow.incrementNonce(nonce, startNonce); // initial value
@@ -103,7 +100,7 @@ const pow = {
     return pow.bufferToBase64(nonce);
   },
 
-  calcTarget(ttl, payloadLen, nonceTrials = PROD_NONCE_TRIALS) {
+  calcTarget(ttl, payloadLen, difficulty = FALLBACK_DIFFICULTY) {
     // payloadLength + NONCE_LEN
     const totalLen = JSBI.add(JSBI.BigInt(payloadLen), JSBI.BigInt(NONCE_LEN));
     // ttl converted to seconds
@@ -119,9 +116,9 @@ const pow = {
     const innerFrac = JSBI.divide(ttlMult, two16);
     // totalLen + innerFrac
     const lenPlusInnerFrac = JSBI.add(totalLen, innerFrac);
-    // nonceTrials * lenPlusInnerFrac
+    // difficulty * lenPlusInnerFrac
     const denominator = JSBI.multiply(
-      JSBI.BigInt(nonceTrials),
+      JSBI.BigInt(difficulty),
       lenPlusInnerFrac
     );
     // 2^64 - 1
diff --git a/libloki/test/metrics.js b/libloki/test/metrics.js
index cdd49059d..222177c52 100644
--- a/libloki/test/metrics.js
+++ b/libloki/test/metrics.js
@@ -3,7 +3,7 @@ let jobId = 0;
 let currentTrace = 0;
 let plotlyDiv;
 const workers = [];
-async function run(messageLength, numWorkers = 1, nonceTrials = 100, ttl = 72) {
+async function run(messageLength, numWorkers = 1, difficulty = 100, ttl = 72) {
   const timestamp = Math.floor(Date.now() / 1000);
   const pubKey =
     '05ec8635a07a13743516c7c9b3412f3e8252efb7fcaf67eb1615ffba62bebc6802';
@@ -29,7 +29,7 @@ async function run(messageLength, numWorkers = 1, nonceTrials = 100, ttl = 72) {
       pubKey,
       data,
       false,
-      nonceTrials,
+      difficulty,
       increment,
       index,
     ]);
@@ -50,12 +50,12 @@ async function run(messageLength, numWorkers = 1, nonceTrials = 100, ttl = 72) {
 
 async function runPoW({
   iteration,
-  nonceTrials,
+  difficulty,
   numWorkers,
   messageLength = 50,
   ttl = 72,
 }) {
-  const name = `W:${numWorkers} - NT: ${nonceTrials} - L:${messageLength} - TTL:${ttl}`;
+  const name = `W:${numWorkers} - NT: ${difficulty} - L:${messageLength} - TTL:${ttl}`;
   Plotly.addTraces(plotlyDiv, {
     y: [],
     type: 'box',
@@ -64,7 +64,7 @@ async function runPoW({
   });
   for (let i = 0; i < iteration; i += 1) {
     // eslint-disable-next-line no-await-in-loop
-    await run(messageLength, numWorkers, nonceTrials, ttl);
+    await run(messageLength, numWorkers, difficulty, ttl);
   }
   currentTrace += 1;
 
@@ -86,9 +86,7 @@ function addPoint(duration) {
 }
 async function startMessageLengthRun() {
   const iteration0 = parseFloat(document.getElementById('iteration0').value);
-  const nonceTrials0 = parseFloat(
-    document.getElementById('nonceTrials0').value
-  );
+  const difficulty0 = parseFloat(document.getElementById('difficulty0').value);
   const numWorkers0 = parseFloat(document.getElementById('numWorkers0').value);
   const messageLengthStart0 = parseFloat(
     document.getElementById('messageLengthStart0').value
@@ -108,7 +106,7 @@ async function startMessageLengthRun() {
     // eslint-disable-next-line no-await-in-loop
     await runPoW({
       iteration: iteration0,
-      nonceTrials: nonceTrials0,
+      difficulty: difficulty0,
       numWorkers: numWorkers0,
       messageLength: l,
       ttl: TTL0,
@@ -117,9 +115,7 @@ async function startMessageLengthRun() {
 }
 async function startNumWorkerRun() {
   const iteration1 = parseFloat(document.getElementById('iteration1').value);
-  const nonceTrials1 = parseFloat(
-    document.getElementById('nonceTrials1').value
-  );
+  const difficulty1 = parseFloat(document.getElementById('difficulty1').value);
   const numWorkersStart1 = parseFloat(
     document.getElementById('numWorkersStart1').value
   );
@@ -138,34 +134,34 @@ async function startNumWorkerRun() {
     // eslint-disable-next-line no-await-in-loop
     await runPoW({
       iteration: iteration1,
-      nonceTrials: nonceTrials1,
+      difficulty: difficulty1,
       numWorkers,
       messageLength: messageLength1,
       ttl: TTL1,
     });
   }
 }
-async function startNonceTrialsRun() {
+async function startDifficultyRun() {
   const iteration2 = parseFloat(document.getElementById('iteration2').value);
   const messageLength2 = parseFloat(
     document.getElementById('messageLength2').value
   );
   const numWorkers2 = parseFloat(document.getElementById('numWorkers2').value);
-  const nonceTrialsStart2 = parseFloat(
-    document.getElementById('nonceTrialsStart2').value
+  const difficultyStart2 = parseFloat(
+    document.getElementById('difficultyStart2').value
   );
-  const nonceTrialsStop2 = parseFloat(
-    document.getElementById('nonceTrialsStop2').value
+  const difficultyStop2 = parseFloat(
+    document.getElementById('difficultyStop2').value
   );
-  const nonceTrialsStep2 = parseFloat(
-    document.getElementById('nonceTrialsStep2').value
+  const difficultyStep2 = parseFloat(
+    document.getElementById('difficultyStep2').value
   );
   const TTL2 = parseFloat(document.getElementById('TTL2').value);
-  for (let n = nonceTrialsStart2; n < nonceTrialsStop2; n += nonceTrialsStep2) {
+  for (let n = difficultyStart2; n < difficultyStop2; n += difficultyStep2) {
     // eslint-disable-next-line no-await-in-loop
     await runPoW({
       iteration: iteration2,
-      nonceTrials: n,
+      difficulty: n,
       numWorkers: numWorkers2,
       messageLength: messageLength2,
       ttl: TTL2,
@@ -174,9 +170,7 @@ async function startNonceTrialsRun() {
 }
 async function starTTLRun() {
   const iteration3 = parseFloat(document.getElementById('iteration3').value);
-  const nonceTrials3 = parseFloat(
-    document.getElementById('nonceTrials3').value
-  );
+  const difficulty3 = parseFloat(document.getElementById('difficulty3').value);
   const messageLength3 = parseFloat(
     document.getElementById('messageLength3').value
   );
@@ -188,7 +182,7 @@ async function starTTLRun() {
     // eslint-disable-next-line no-await-in-loop
     await runPoW({
       iteration: iteration3,
-      nonceTrials: nonceTrials3,
+      difficulty: difficulty3,
       numWorkers: numWorkers3,
       messageLength: messageLength3,
       ttl,
@@ -216,7 +210,7 @@ async function start(index) {
       await startNumWorkerRun();
       break;
     case 2:
-      await startNonceTrialsRun();
+      await startDifficultyRun();
       break;
     case 3:
       await starTTLRun();
diff --git a/libtextsecure/errors.js b/libtextsecure/errors.js
index 78f9fcfb1..0ad331080 100644
--- a/libtextsecure/errors.js
+++ b/libtextsecure/errors.js
@@ -222,6 +222,19 @@
     }
   }
 
+  function WrongDifficultyError(newDifficulty) {
+    this.name = 'WrongDifficultyError';
+    this.newDifficulty = newDifficulty;
+
+    Error.call(this, this.name);
+
+    // Maintains proper stack trace, where our error was thrown (only available on V8)
+    //   via https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Error
+    if (Error.captureStackTrace) {
+      Error.captureStackTrace(this);
+    }
+  }
+
   window.textsecure.UnregisteredUserError = UnregisteredUserError;
   window.textsecure.SendMessageNetworkError = SendMessageNetworkError;
   window.textsecure.IncomingIdentityKeyError = IncomingIdentityKeyError;
@@ -237,4 +250,5 @@
   window.textsecure.HTTPError = HTTPError;
   window.textsecure.NotFoundError = NotFoundError;
   window.textsecure.WrongSwarmError = WrongSwarmError;
+  window.textsecure.WrongDifficultyError = WrongDifficultyError;
 })();
diff --git a/main.js b/main.js
index 9cf854a7f..eaad6252c 100644
--- a/main.js
+++ b/main.js
@@ -156,6 +156,7 @@ function prepareURL(pathSegments, moreKeys) {
       cdnUrl: config.get('cdnUrl'),
       snodeServerPort: config.get('snodeServerPort'),
       localServerPort: config.get('localServerPort'),
+      defaultPoWDifficulty: config.get('defaultPoWDifficulty'),
       certificateAuthority: config.get('certificateAuthority'),
       environment: config.environment,
       node_version: process.versions.node,
diff --git a/preload.js b/preload.js
index 90c7f4885..5cad3915d 100644
--- a/preload.js
+++ b/preload.js
@@ -22,6 +22,7 @@ if (config.appInstance) {
 }
 
 window.platform = process.platform;
+window.getDefaultPoWDifficulty = () => config.defaultPoWDifficulty;
 window.getTitle = () => title;
 window.getEnvironment = () => config.environment;
 window.getAppInstance = () => config.appInstance;