diff --git a/authboss_test.go b/authboss_test.go
index 709a07a..a90b412 100644
--- a/authboss_test.go
+++ b/authboss_test.go
@@ -22,30 +22,6 @@ func TestAuthBossInit(t *testing.T) {
 	}
 }
 
-func TestAuthBossRouter(t *testing.T) {
-	NewConfig()
-	Cfg.Storer = mockStorer{}
-	Cfg.CookieStoreMaker = func(_ http.ResponseWriter, _ *http.Request) ClientStorer {
-		return mockClientStore{}
-	}
-	Cfg.SessionStoreMaker = SessionStoreMaker(Cfg.CookieStoreMaker)
-	Cfg.MountPath = "/candycanes"
-
-	if err := Init(); err != nil {
-		t.Error("Unexpected error:", err)
-	}
-	router := NewRouter()
-
-	r, _ := http.NewRequest("GET", "/candycanes/testroute", nil)
-	response := httptest.NewRecorder()
-
-	router.ServeHTTP(response, r)
-
-	if response.Header().Get("testhandler") != "test" {
-		t.Error("Expected a header to have been set.")
-	}
-}
-
 func TestAuthBossCurrentUser(t *testing.T) {
 	NewConfig()
 	Cfg.Storer = mockStorer{"joe": Attributes{"email": "john@john.com", "password": "lies"}}
diff --git a/config.go b/config.go
index 6a3d868..6d24cd6 100644
--- a/config.go
+++ b/config.go
@@ -4,6 +4,7 @@ import (
 	"html/template"
 	"io"
 	"io/ioutil"
+	"net/http"
 	"net/smtp"
 	"time"
 
@@ -32,6 +33,13 @@ type Config struct {
 	LayoutEmail     *template.Template
 	LayoutDataMaker ViewDataMaker
 
+	// ErrorHandler handles would be 500 errors.
+	ErrorHandler http.Handler
+	// BadRequestHandler handles would be 400 errors.
+	BadRequestHandler http.Handler
+	// NotFoundHandler handles would be 404 errors.
+	NotFoundHandler http.Handler
+
 	AuthLogoutRoute       string
 	AuthLoginSuccessRoute string
 
diff --git a/confirm/confirm.go b/confirm/confirm.go
index af9f57d..e2038cb 100644
--- a/confirm/confirm.go
+++ b/confirm/confirm.go
@@ -149,7 +149,7 @@ func (c *Confirm) confirmHandler(ctx *authboss.Context, w http.ResponseWriter, r
 	toHash, err := base64.URLEncoding.DecodeString(token)
 	if err != nil {
 		return authboss.ErrAndRedirect{
-			Endpoint: "/", Err: fmt.Errorf("confirm: token failed to decode %q => %v\n", token, err),
+			Location: "/", Err: fmt.Errorf("confirm: token failed to decode %q => %v\n", token, err),
 		}
 	}
 
@@ -158,7 +158,7 @@ func (c *Confirm) confirmHandler(ctx *authboss.Context, w http.ResponseWriter, r
 	dbTok := base64.StdEncoding.EncodeToString(sum[:])
 	user, err := authboss.Cfg.Storer.(ConfirmStorer).ConfirmUser(dbTok)
 	if err == authboss.ErrUserNotFound {
-		return authboss.ErrAndRedirect{Endpoint: "/", Err: errors.New("confirm: token not found")}
+		return authboss.ErrAndRedirect{Location: "/", Err: errors.New("confirm: token not found")}
 	} else if err != nil {
 		return err
 	}
diff --git a/confirm/confirm_test.go b/confirm/confirm_test.go
index 92b736b..886665a 100644
--- a/confirm/confirm_test.go
+++ b/confirm/confirm_test.go
@@ -141,10 +141,10 @@ func TestConfirm_ConfirmHandlerErrors(t *testing.T) {
 	}{
 		{"http://localhost", false, authboss.ClientDataErr{FormValueConfirm}},
 		{"http://localhost?cnf=c$ats", false,
-			authboss.ErrAndRedirect{Endpoint: "/", Err: errors.New("confirm: token failed to decode \"c$ats\" => illegal base64 data at input byte 1\n")},
+			authboss.ErrAndRedirect{Location: "/", Err: errors.New("confirm: token failed to decode \"c$ats\" => illegal base64 data at input byte 1\n")},
 		},
 		{"http://localhost?cnf=SGVsbG8sIHBsYXlncm91bmQ=", false,
-			authboss.ErrAndRedirect{Endpoint: "/", Err: errors.New(`confirm: token not found`)},
+			authboss.ErrAndRedirect{Location: "/", Err: errors.New(`confirm: token not found`)},
 		},
 	}
 
diff --git a/errors.go b/errors.go
index 30855da..6c61788 100644
--- a/errors.go
+++ b/errors.go
@@ -40,13 +40,13 @@ func (c ClientDataErr) Error() string {
 // be to redirect.
 type ErrAndRedirect struct {
 	Err          error
-	Endpoint     string
+	Location     string
 	FlashSuccess string
 	FlashError   string
 }
 
 func (e ErrAndRedirect) Error() string {
-	return fmt.Sprintf("Error: %v, Redirecting to: %s", e.Err, e.Endpoint)
+	return fmt.Sprintf("Error: %v, Redirecting to: %s", e.Err, e.Location)
 }
 
 // RenderErr represents an error that occured during rendering
diff --git a/module_test.go b/module_test.go
index 0138285..54856e1 100644
--- a/module_test.go
+++ b/module_test.go
@@ -23,21 +23,11 @@ func testHandler(ctx *Context, w http.ResponseWriter, r *http.Request) error {
 	return nil
 }
 
-func (t *testModule) Initialize() error {
-	return nil
-}
-
-func (t *testModule) Routes() RouteTable {
-	return t.r
-}
-
-func (t *testModule) Storage() StorageOptions {
-	return t.s
-}
+func (t *testModule) Initialize() error       { return nil }
+func (t *testModule) Routes() RouteTable      { return t.r }
+func (t *testModule) Storage() StorageOptions { return t.s }
 
 func TestRegister(t *testing.T) {
-	t.Parallel()
-
 	// RegisterModule called by TestMain.
 
 	if _, ok := modules["testmodule"]; !ok {
@@ -50,8 +40,6 @@ func TestRegister(t *testing.T) {
 }
 
 func TestLoadedModules(t *testing.T) {
-	t.Parallel()
-
 	// RegisterModule called by TestMain.
 
 	loadedMods := LoadedModules()
diff --git a/router.go b/router.go
index 3b4a81c..fd647ab 100644
--- a/router.go
+++ b/router.go
@@ -2,6 +2,7 @@ package authboss
 
 import (
 	"fmt"
+	"io"
 	"net/http"
 	"path"
 )
@@ -23,6 +24,15 @@ func NewRouter() http.Handler {
 		}
 	}
 
+	mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
+		if Cfg.NotFoundHandler != nil {
+			Cfg.NotFoundHandler.ServeHTTP(w, r)
+		} else {
+			w.WriteHeader(http.StatusNotFound)
+			io.WriteString(w, "404 Page not found")
+		}
+	})
+
 	return mux
 }
 
@@ -46,22 +56,29 @@ func (c contextRoute) ServeHTTP(w http.ResponseWriter, r *http.Request) {
 	}
 
 	fmt.Fprintf(Cfg.LogWriter, "Error Occurred at %s: %v", r.URL.Path, err)
+
 	switch e := err.(type) {
-	case AttributeErr:
-		w.WriteHeader(http.StatusInternalServerError)
-	case ClientDataErr:
-		w.WriteHeader(http.StatusBadRequest)
 	case ErrAndRedirect:
 		if len(e.FlashSuccess) > 0 {
-			ctx.CookieStorer.Put(FlashSuccessKey, e.FlashSuccess)
+			ctx.SessionStorer.Put(FlashSuccessKey, e.FlashSuccess)
 		}
 		if len(e.FlashError) > 0 {
-			ctx.CookieStorer.Put(FlashErrorKey, e.FlashError)
+			ctx.SessionStorer.Put(FlashErrorKey, e.FlashError)
+		}
+		http.Redirect(w, r, e.Location, http.StatusFound)
+	case ClientDataErr:
+		if Cfg.BadRequestHandler != nil {
+			Cfg.BadRequestHandler.ServeHTTP(w, r)
+		} else {
+			w.WriteHeader(http.StatusBadRequest)
+			io.WriteString(w, "400 Bad request")
 		}
-		http.Redirect(w, r, e.Endpoint, http.StatusTemporaryRedirect)
-	case RenderErr:
-		w.WriteHeader(http.StatusInternalServerError)
 	default:
-		w.WriteHeader(http.StatusInternalServerError)
+		if Cfg.ErrorHandler != nil {
+			Cfg.ErrorHandler.ServeHTTP(w, r)
+		} else {
+			w.WriteHeader(http.StatusInternalServerError)
+			io.WriteString(w, "500 An error has occurred")
+		}
 	}
 }
diff --git a/router_test.go b/router_test.go
new file mode 100644
index 0000000..70b6c76
--- /dev/null
+++ b/router_test.go
@@ -0,0 +1,200 @@
+package authboss
+
+import (
+	"bytes"
+	"errors"
+	"net/http"
+	"net/http/httptest"
+	"strings"
+	"testing"
+)
+
+type testRouterMod struct {
+	handler HandlerFunc
+	routes  RouteTable
+}
+
+func (t testRouterMod) Initialize() error       { return nil }
+func (t testRouterMod) Routes() RouteTable      { return t.routes }
+func (t testRouterMod) Storage() StorageOptions { return nil }
+
+func testRouterSetup() (http.Handler, *bytes.Buffer) {
+	Cfg = NewConfig()
+	Cfg.MountPath = "/prefix"
+	Cfg.SessionStoreMaker = func(w http.ResponseWriter, r *http.Request) ClientStorer { return mockClientStore{} }
+	Cfg.CookieStoreMaker = func(w http.ResponseWriter, r *http.Request) ClientStorer { return mockClientStore{} }
+	logger := &bytes.Buffer{}
+	Cfg.LogWriter = logger
+
+	return NewRouter(), logger
+}
+
+func testRouterCallbackSetup(path string, h HandlerFunc) (w *httptest.ResponseRecorder, r *http.Request) {
+	modules = map[string]Modularizer{
+		"test": testRouterMod{
+			routes: map[string]HandlerFunc{
+				path: h,
+			},
+		},
+	}
+
+	w = httptest.NewRecorder()
+	r, _ = http.NewRequest("GET", "http://localhost/prefix"+path, nil)
+
+	return w, r
+}
+
+func TestRouter(t *testing.T) {
+	called := false
+
+	w, r := testRouterCallbackSetup("/called", func(ctx *Context, w http.ResponseWriter, r *http.Request) error {
+		called = true
+		return nil
+	})
+
+	router, _ := testRouterSetup()
+
+	router.ServeHTTP(w, r)
+
+	if !called {
+		t.Error("Expected handler to be called.")
+	}
+}
+
+func TestRouter_NotFound(t *testing.T) {
+	router, _ := testRouterSetup()
+	w := httptest.NewRecorder()
+	r, _ := http.NewRequest("GET", "http://localhost/wat", nil)
+
+	router.ServeHTTP(w, r)
+	if w.Code != http.StatusNotFound {
+		t.Error("Wrong code:", w.Code)
+	}
+	if body := w.Body.String(); body != "404 Page not found" {
+		t.Error("Wrong body:", body)
+	}
+
+	called := false
+	Cfg.NotFoundHandler = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+		called = true
+	})
+
+	router.ServeHTTP(w, r)
+	if !called {
+		t.Error("Should be called.")
+	}
+}
+
+func TestRouter_BadRequest(t *testing.T) {
+	err := ClientDataErr{"what"}
+	w, r := testRouterCallbackSetup("/badrequest",
+		func(ctx *Context, w http.ResponseWriter, r *http.Request) error {
+			return err
+		},
+	)
+
+	router, logger := testRouterSetup()
+	logger.Reset()
+	router.ServeHTTP(w, r)
+
+	if w.Code != http.StatusBadRequest {
+		t.Error("Wrong code:", w.Code)
+	}
+	if body := w.Body.String(); body != "400 Bad request" {
+		t.Error("Wrong body:", body)
+	}
+
+	if str := logger.String(); !strings.Contains(str, err.Error()) {
+		t.Error(str)
+	}
+
+	called := false
+	Cfg.BadRequestHandler = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+		called = true
+	})
+
+	logger.Reset()
+	router.ServeHTTP(w, r)
+	if !called {
+		t.Error("Should be called.")
+	}
+
+	if str := logger.String(); !strings.Contains(str, err.Error()) {
+		t.Error(str)
+	}
+}
+
+func TestRouter_Error(t *testing.T) {
+	err := errors.New("error")
+	w, r := testRouterCallbackSetup("/error",
+		func(ctx *Context, w http.ResponseWriter, r *http.Request) error {
+			return err
+		},
+	)
+
+	router, logger := testRouterSetup()
+	logger.Reset()
+	router.ServeHTTP(w, r)
+
+	if w.Code != http.StatusInternalServerError {
+		t.Error("Wrong code:", w.Code)
+	}
+	if body := w.Body.String(); body != "500 An error has occurred" {
+		t.Error("Wrong body:", body)
+	}
+
+	if str := logger.String(); !strings.Contains(str, err.Error()) {
+		t.Error(str)
+	}
+
+	called := false
+	Cfg.ErrorHandler = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+		called = true
+	})
+
+	logger.Reset()
+	router.ServeHTTP(w, r)
+	if !called {
+		t.Error("Should be called.")
+	}
+
+	if str := logger.String(); !strings.Contains(str, err.Error()) {
+		t.Error(str)
+	}
+}
+
+func TestRouter_Redirect(t *testing.T) {
+	err := ErrAndRedirect{
+		Err:          errors.New("error"),
+		Location:     "/",
+		FlashSuccess: "yay",
+		FlashError:   "nay",
+	}
+
+	w, r := testRouterCallbackSetup("/error",
+		func(ctx *Context, w http.ResponseWriter, r *http.Request) error {
+			return err
+		},
+	)
+
+	router, logger := testRouterSetup()
+
+	session := mockClientStore{}
+	Cfg.SessionStoreMaker = func(w http.ResponseWriter, r *http.Request) ClientStorer { return session }
+
+	logger.Reset()
+	router.ServeHTTP(w, r)
+
+	if w.Code != http.StatusFound {
+		t.Error("Wrong code:", w.Code)
+	}
+	if loc := w.Header().Get("Location"); loc != err.Location {
+		t.Error("Wrong location:", loc)
+	}
+	if succ, ok := session.Get(FlashSuccessKey); !ok || succ != err.FlashSuccess {
+		t.Error(succ, ok)
+	}
+	if fail, ok := session.Get(FlashErrorKey); !ok || fail != err.FlashError {
+		t.Error(fail, ok)
+	}
+}