diff --git a/ChangeLog b/ChangeLog
new file mode 100644
--- /dev/null
+++ b/ChangeLog
@@ -0,0 +1,15 @@
+2013-11-29  Sam Truzjan  <pxqr.sta@gmail.com>
+
+	* 0.2.0.0: Allow to catch decoding errors in pure code.
+
+2013-10-31  Sam Truzjan  <pxqr.sta@gmail.com>
+
+	* 0.1.1.1: Update URLs after migration.
+
+2013-10-04  Sam Truzjan  <pxqr.sta@gmail.com>
+
+	* 0.1.1.0: Added lenient decoding.
+
+2013-09-27  Sam Truzjan  <pxqr.sta@gmail.com>
+
+	* 0.1.0.0: Initial version.
diff --git a/base32-bytestring.cabal b/base32-bytestring.cabal
--- a/base32-bytestring.cabal
+++ b/base32-bytestring.cabal
@@ -1,5 +1,5 @@
 name:                base32-bytestring
-version:             0.1.1.1
+version:             0.2.0.0
 license:             BSD3
 license-file:        LICENSE
 author:              Sam Truzjan
@@ -18,7 +18,7 @@
   The package API is similar to base64-bytestring.
 
 extra-source-files:    README.md
-                     , changelog
+                     , ChangeLog
 
 source-repository head
   type:                git
@@ -29,7 +29,7 @@
   type:                git
   location:            git://github.com/pxqr/base32-bytestring.git
   branch:              master
-  tag:                 v0.1.1.1
+  tag:                 v0.2.0.0
 
 library
   default-language:    Haskell2010
@@ -46,7 +46,7 @@
 
 test-suite spec
   default-language:    Haskell2010
-  default-extensions:  OverloadedStrings
+  default-extensions:
   type:                exitcode-stdio-1.0
   hs-source-dirs:      tests
   main-is:             Spec.hs
diff --git a/changelog b/changelog
deleted file mode 100644
--- a/changelog
+++ /dev/null
@@ -1,3 +0,0 @@
-* 0.1.0.0: Initial version.
-* 0.1.1.0: Added lenient decoding.
-* 0.1.1.1: Update URLs after migration.
diff --git a/src/Data/ByteString/Base32.hs b/src/Data/ByteString/Base32.hs
--- a/src/Data/ByteString/Base32.hs
+++ b/src/Data/ByteString/Base32.hs
@@ -37,7 +37,7 @@
 encTable :: EncTable
 encTable = BS.pack $ L.map encW5 [0..31]
 
--- | Encode a bytestring into base32 form.
+-- | Encode an arbitrary bytestring into (upper case) base32 form.
 encode :: ByteString -> Base32
 encode = unpack5 encTable
 
@@ -56,11 +56,11 @@
 decTable = BS.pack $ L.map decW5 [minBound .. maxBound]
 
 -- | Decode a base32 encoded bytestring. This functions is
--- case-insensitive and do not requires correct padding.
-decode :: Base32 -> ByteString
+-- case-insensitive and do not require correct padding.
+decode :: Base32 -> Either String ByteString
 decode = pack5 decTable
 
 -- | The same as 'decode' but with additional leniency: decodeLenient
 -- will skip non-alphabet characters.
-decodeLenient :: Base32 -> ByteString
+decodeLenient :: Base32 -> Either String ByteString
 decodeLenient = pack5Lenient decTable
diff --git a/src/Data/ByteString/Base32/Hex.hs b/src/Data/ByteString/Base32/Hex.hs
--- a/src/Data/ByteString/Base32/Hex.hs
+++ b/src/Data/ByteString/Base32/Hex.hs
@@ -36,7 +36,7 @@
 encTable :: EncTable
 encTable = BS.pack $ L.map encW5 [0..31]
 
--- | Encode a bytestring into base32hex form.
+-- | Encode an arbitrary bytestring into (upper case) base32hex form.
 encode :: ByteString -> Base32Hex
 encode = unpack5 encTable
 
@@ -55,10 +55,10 @@
 
 -- | Decode a base32hex encoded bytestring. This functions is
 -- case-insensitive and do not requires correct padding.
-decode :: Base32Hex -> ByteString
+decode :: Base32Hex -> Either String ByteString
 decode = pack5 decTable
 
 -- | The same as 'decode' but with additional leniency: decodeLenient
 -- will skip non-alphabet characters.
-decodeLenient :: Base32Hex -> ByteString
+decodeLenient :: Base32Hex -> Either String ByteString
 decodeLenient = pack5Lenient decTable
diff --git a/src/Data/ByteString/Base32/Internal.hs b/src/Data/ByteString/Base32/Internal.hs
--- a/src/Data/ByteString/Base32/Internal.hs
+++ b/src/Data/ByteString/Base32/Internal.hs
@@ -22,6 +22,7 @@
        , invIx
        ) where
 
+import Control.Exception hiding (mask)
 import Data.Bits.Extras
 import Data.ByteString as BS
 import Data.ByteString.Internal as BS
@@ -151,9 +152,17 @@
 invIx :: Word5
 invIx = 255
 
-pack5Ptr :: Ptr Word5 -> ByteString -> ByteString
+type Result = Either String
+
+cleanup :: IO a -> Result a
+cleanup io = unsafePerformIO $
+    catch (io >>= evaluate >>= return . Right) handler
+  where
+    handler (ErrorCall msg) = return (Left msg)
+
+pack5Ptr :: Ptr Word5 -> ByteString -> Result ByteString
 pack5Ptr !tbl bs @ (PS fptr off sz) =
-  unsafePerformIO $ do
+  cleanup $ do
     let packedSize = dstSize $ BS.length bs
     BS.createAndTrim packedSize $ \ dst -> do
         withForeignPtr fptr $ \ ptr -> do
@@ -162,7 +171,7 @@
   where
     lookupTable :: Word8 -> Word5
     lookupTable ix
-        | x == invIx = error $ "base32: decode: invalid character" ++ show ix
+        | x == invIx = error $ show (w2c ix) ++ " is not base32 character"
         | otherwise  = x
       where x = inlinePerformIO (peekByteOff tbl (fromIntegral ix))
     {-# INLINE lookupTable #-}
@@ -221,7 +230,7 @@
 
 type DecTable = ByteString
 
-pack5 :: DecTable -> ByteString -> ByteString
+pack5 :: DecTable -> ByteString -> Result ByteString
 pack5 (PS fptr off len) bs
   | len /= 256
   = error $ "base32: pack5: invalid lookup table size " ++ show len
@@ -238,7 +247,7 @@
 isInAlphabet !tbl !ix =
   inlinePerformIO (peekByteOff tbl (fromIntegral ix)) /= invIx
 
-pack5Lenient :: DecTable -> ByteString -> ByteString
+pack5Lenient :: DecTable -> ByteString -> Either String ByteString
 pack5Lenient tbl @ (PS fptr _ _) bs =
   unsafePerformIO $ do
     withForeignPtr fptr $ \ !tbl_ptr -> do
