diff --git a/CHANGELOG.md b/CHANGELOG.md
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,9 @@
 # Revision history for http-interchange
 
+## 0.3.2.2 -- 2024-08-23
+
+* Allow all RFC 7230 tchar in header name
+
 ## 0.3.2.1 -- 2024-02-07
 
 * List test suite file dependencies in `extra-source-files` so that Hackage
diff --git a/golden/response/003.input b/golden/response/003.input
new file mode 100644
--- /dev/null
+++ b/golden/response/003.input
@@ -0,0 +1,19 @@
+HTTP/1.1 200 OK
+Content-Length: 0
+Access-Control-Allow-Origin: *
+Access-Control-Allow-Credentials: true
+Allow: GET, HEAD, POST, PUT, DELETE, TRACE, OPTIONS, SCRIPT
+Access-Control-Allow-Methods: GET, HEAD, POST, PUT, DELETE, TRACE, OPTIONS, SCRIPT
+Access-Control-Allow-Headers: Authorization,Content-Type,Content-Encoding,Accept,X-Netflix.application.name,X-Netflix.application.version,X-Netflix.esn,X-Netflix.device.type,X-Netflix.certification.version,X-Netflix.request.uuid,X-Netflix.originating.request.uuid,X-Netflix.user.id,X-Netflix.oauth.consumer.key,X-Netflix.oauth.token,X-Netflix.ichnaea.request.type,X-Netflix.Request.Routing,X-NETFLIX-PREAPP-PARTNER-ID,X-NETFLIX-PREAPP-INTEGRITY-VALUE,X-Netflix.Request.Priority,X-Netflix.Retry.Client.Policy,X-Netflix.Client.Request.Name,X-Netflix.Request.Retry.Policy,X-Netflix.Request.Retry.Policy.Default,X-Netflix.request.client.user.guid,X-Netflix.Request.NonJson.Headers,X-Netflix.esnPrefix,X-Netflix.browserName,X-Netflix.browserVersion,X-Netflix.osName,X-Netflix.osVersion,X-Netflix.uiVersion,X-Netflix.clientType,X-NETFLIX-PERSONALIZATION-ID,X-NETFLIX-DET-TOKEN,X-NETFLIX-DET-PARTNER-PAI,X-NETFLIX-RESPONSE-OVERRIDDEN,X-NETFLIX-DET-DEPRECATION,X-Netflix.context.locales,X-Netflix.context.ui-flavor,X-Netflix.context.ale.token,X-Netflix.context.app-version,X-Netflix.context.schema-variant,X-Netflix.Request.toplevel.uuid,f9hi42t,x-session,X-Netflix.Request.Attempt
+Access-Control-Expose-Headers: X-Netflix.Retry.Server.Policy,X-Netflix.Response.Tag,X-Netflix.Geo.Info,X-Netflix.request.inbound.identity.changed,Via,X-Netflix.Retry.Server.Policy.retryAfterSeconds,X-Netflix.Retry.Server.Policy.maxRetries,X-Ftl-Error,X-Netflix.uiVersion,X-Netflix.Playapi.Backoff
+Via: 1.1 i-08d45052140120818 (us-east-2)
+X-Xss-Protection: 1; mode=block; report=https://www.netflix.com/ichnaea/log/freeform/xssreport
+X-Content-Type-Options: nosniff
+Strict-Transport-Security: max-age=31536000; includeSubDomains
+X-Originating-URL: http://www.netflix.com/
+Set-Cookie: nfvdid=BQFmAAEBEFU_lDUnKbaHRi5tl0Mhqc5ALJIuWadL1PlbBLQGl0n-1n-GCB41ux6moG0JMBj22dNaLsaXeB3DmLHrc6SUwOP43peCxZ-9EQNtFxz7gqTC0w%3D%3D; Domain=.netflix.com; Path=/; Max-Age=31536000
+Set-Cookie: SecureNetflixId=v%3D3%26mac%3DAQEAEQABABRXxJiCXjMlnNAESuzV2TyKuP3eS3p8YJc.%26dt%3D1724424807026; Domain=.netflix.com; Expires=Sat, 23 Aug 2025 14:53:27 GMT; Path=/; HttpOnly; Secure; SameSite=Strict
+Set-Cookie: NetflixId=v%3D3%26ct%3DBgjHlOvcAxK_AQ-lxAypPWxfZpzrloHvpi11GUHAmWl-qgv5VMtuoq9QRvkhHh1zrdLY_ALqrfLQh6YFvx1vjTj6Bwwaxa3_5dZMTkdMBJ9jBUR2nqz-uANNz8MLx210xS7_tyfEk8bTLNGPLXfUnE-XsqKT-Lt5x6A9VZzCrjsHtWOPObAA8ast0yC-FfMJT-1cxBnBDBd9LQeFysgRrRi3VjA6bb-KXqABXABbuXKS0iY1e8wgqs_hv4cvHBciaFkIuDO1GH4vGAYiDgoMorBJMaCVqFf0KRMy; Domain=.netflix.com; Expires=Sat, 23 Aug 2025 14:53:27 GMT; Path=/; HttpOnly; Secure; SameSite=Lax
+X-Netflix.nfstatus: 1_1
+X-Netflix.proxy.execution-time: 61
+
diff --git a/golden/response/003.output b/golden/response/003.output
new file mode 100644
--- /dev/null
+++ b/golden/response/003.output
@@ -0,0 +1,59 @@
+Response
+  { statusLine =
+      StatusLine { statusCode = 200 , statusReason = "OK" }
+  , headers =
+      [ Header { name = "Content-Length" , value = "0" }
+      , Header { name = "Access-Control-Allow-Origin" , value = "*" }
+      , Header
+          { name = "Access-Control-Allow-Credentials" , value = "true" }
+      , Header
+          { name = "Allow"
+          , value = "GET, HEAD, POST, PUT, DELETE, TRACE, OPTIONS, SCRIPT"
+          }
+      , Header
+          { name = "Access-Control-Allow-Methods"
+          , value = "GET, HEAD, POST, PUT, DELETE, TRACE, OPTIONS, SCRIPT"
+          }
+      , Header
+          { name = "Access-Control-Allow-Headers"
+          , value =
+              "Authorization,Content-Type,Content-Encoding,Accept,X-Netflix.application.name,X-Netflix.application.version,X-Netflix.esn,X-Netflix.device.type,X-Netflix.certification.version,X-Netflix.request.uuid,X-Netflix.originating.request.uuid,X-Netflix.user.id,X-Netflix.oauth.consumer.key,X-Netflix.oauth.token,X-Netflix.ichnaea.request.type,X-Netflix.Request.Routing,X-NETFLIX-PREAPP-PARTNER-ID,X-NETFLIX-PREAPP-INTEGRITY-VALUE,X-Netflix.Request.Priority,X-Netflix.Retry.Client.Policy,X-Netflix.Client.Request.Name,X-Netflix.Request.Retry.Policy,X-Netflix.Request.Retry.Policy.Default,X-Netflix.request.client.user.guid,X-Netflix.Request.NonJson.Headers,X-Netflix.esnPrefix,X-Netflix.browserName,X-Netflix.browserVersion,X-Netflix.osName,X-Netflix.osVersion,X-Netflix.uiVersion,X-Netflix.clientType,X-NETFLIX-PERSONALIZATION-ID,X-NETFLIX-DET-TOKEN,X-NETFLIX-DET-PARTNER-PAI,X-NETFLIX-RESPONSE-OVERRIDDEN,X-NETFLIX-DET-DEPRECATION,X-Netflix.context.locales,X-Netflix.context.ui-flavor,X-Netflix.context.ale.token,X-Netflix.context.app-version,X-Netflix.context.schema-variant,X-Netflix.Request.toplevel.uuid,f9hi42t,x-session,X-Netflix.Request.Attempt"
+          }
+      , Header
+          { name = "Access-Control-Expose-Headers"
+          , value =
+              "X-Netflix.Retry.Server.Policy,X-Netflix.Response.Tag,X-Netflix.Geo.Info,X-Netflix.request.inbound.identity.changed,Via,X-Netflix.Retry.Server.Policy.retryAfterSeconds,X-Netflix.Retry.Server.Policy.maxRetries,X-Ftl-Error,X-Netflix.uiVersion,X-Netflix.Playapi.Backoff"
+          }
+      , Header
+          { name = "Via" , value = "1.1 i-08d45052140120818 (us-east-2)" }
+      , Header
+          { name = "X-Xss-Protection"
+          , value =
+              "1; mode=block; report=https://www.netflix.com/ichnaea/log/freeform/xssreport"
+          }
+      , Header { name = "X-Content-Type-Options" , value = "nosniff" }
+      , Header
+          { name = "Strict-Transport-Security"
+          , value = "max-age=31536000; includeSubDomains"
+          }
+      , Header
+          { name = "X-Originating-URL" , value = "http://www.netflix.com/" }
+      , Header
+          { name = "Set-Cookie"
+          , value =
+              "nfvdid=BQFmAAEBEFU_lDUnKbaHRi5tl0Mhqc5ALJIuWadL1PlbBLQGl0n-1n-GCB41ux6moG0JMBj22dNaLsaXeB3DmLHrc6SUwOP43peCxZ-9EQNtFxz7gqTC0w%3D%3D; Domain=.netflix.com; Path=/; Max-Age=31536000"
+          }
+      , Header
+          { name = "Set-Cookie"
+          , value =
+              "SecureNetflixId=v%3D3%26mac%3DAQEAEQABABRXxJiCXjMlnNAESuzV2TyKuP3eS3p8YJc.%26dt%3D1724424807026; Domain=.netflix.com; Expires=Sat, 23 Aug 2025 14:53:27 GMT; Path=/; HttpOnly; Secure; SameSite=Strict"
+          }
+      , Header
+          { name = "Set-Cookie"
+          , value =
+              "NetflixId=v%3D3%26ct%3DBgjHlOvcAxK_AQ-lxAypPWxfZpzrloHvpi11GUHAmWl-qgv5VMtuoq9QRvkhHh1zrdLY_ALqrfLQh6YFvx1vjTj6Bwwaxa3_5dZMTkdMBJ9jBUR2nqz-uANNz8MLx210xS7_tyfEk8bTLNGPLXfUnE-XsqKT-Lt5x6A9VZzCrjsHtWOPObAA8ast0yC-FfMJT-1cxBnBDBd9LQeFysgRrRi3VjA6bb-KXqABXABbuXKS0iY1e8wgqs_hv4cvHBciaFkIuDO1GH4vGAYiDgoMorBJMaCVqFf0KRMy; Domain=.netflix.com; Expires=Sat, 23 Aug 2025 14:53:27 GMT; Path=/; HttpOnly; Secure; SameSite=Lax"
+          }
+      , Header { name = "X-Netflix.nfstatus" , value = "1_1" }
+      , Header { name = "X-Netflix.proxy.execution-time" , value = "61" }
+      ]
+  }
diff --git a/http-interchange.cabal b/http-interchange.cabal
--- a/http-interchange.cabal
+++ b/http-interchange.cabal
@@ -1,6 +1,6 @@
 cabal-version:      3.0
 name:               http-interchange
-version:            0.3.2.1
+version:            0.3.2.2
 license:            BSD-3-Clause
 license-file:       LICENSE
 author:             Andrew Martin
diff --git a/src/Http/Header.hs b/src/Http/Header.hs
--- a/src/Http/Header.hs
+++ b/src/Http/Header.hs
@@ -1,4 +1,5 @@
 {-# LANGUAGE LambdaCase #-}
+{-# LANGUAGE PatternSynonyms #-}
 
 module Http.Header
   ( Header (..)
@@ -15,6 +16,7 @@
 import Data.Bytes.Types (Bytes (Bytes))
 import Data.Primitive (ByteArray (ByteArray), SmallArray, SmallMutableArray)
 import Data.Text (Text)
+import Data.Word (Word8)
 
 import Data.Bytes qualified as Bytes
 import Data.Bytes.Builder qualified as Builder
@@ -74,9 +76,62 @@
           parserHeaderStep (ix + 1) (n - 1) dst
         else Parser.fail ()
 
+pattern Bang :: Word8
+pattern Bang = 0x21
+
+pattern Pound :: Word8
+pattern Pound = 0x23
+
+pattern Dollar :: Word8
+pattern Dollar = 0x24
+
+pattern Percent :: Word8
+pattern Percent = 0x25
+
+pattern Ampersand :: Word8
+pattern Ampersand = 0x26
+
+pattern SingleQuote :: Word8
+pattern SingleQuote = 0x27
+
+pattern Asterisk :: Word8
+pattern Asterisk = 0x2A
+
+pattern Plus :: Word8
+pattern Plus = 0x2B
+
+pattern Hyphen :: Word8
+pattern Hyphen = 0x2D
+
+pattern Period :: Word8
+pattern Period = 0x2E
+
+pattern Caret :: Word8
+pattern Caret = 0x5E
+
+pattern Underscore :: Word8
+pattern Underscore = 0x5F
+
+pattern Backtick :: Word8
+pattern Backtick = 0x60
+
+pattern Pipe :: Word8
+pattern Pipe = 0x7C
+
+pattern Twiddle :: Word8
+pattern Twiddle = 0x7E
+
+pattern HorizontalTab :: Word8
+pattern HorizontalTab = 0x09
+
 {- | Parse a single HTTP header including the trailing CRLF sequence.
 From RFC 7230:
 
+> token          = 1*tchar
+> tchar          = "!" / "#" / "$" / "%" / "&" / "'" / "*"
+>                / "+" / "-" / "." / "^" / "_" / "`" / "|" / "~" 
+>                / DIGIT / ALPHA
+> 
 > header-field   = field-name ":" OWS field-value OWS
 > field-name     = token
 > field-value    = *( field-content / obs-fold )
@@ -85,19 +140,32 @@
 -}
 parser :: Parser () s Header
 parser = do
-  -- Header name may contain: a-z, A-Z, 0-9, underscore, hyphen
+  -- Header name may contain: a-z, A-Z, 0-9, several different symbols
   !name <- Parser.takeWhile $ \c ->
     (c >= 0x41 && c <= 0x5A)
       || (c >= 0x61 && c <= 0x7A)
       || (c >= 0x30 && c <= 0x39)
-      || c == 0x2D
-      || c == 0x5F
+      || c == Bang
+      || c == Pound
+      || c == Dollar
+      || c == Percent
+      || c == Ampersand
+      || c == SingleQuote
+      || c == Asterisk
+      || c == Plus
+      || c == Hyphen
+      || c == Period
+      || c == Caret
+      || c == Underscore
+      || c == Backtick
+      || c == Pipe
+      || c == Twiddle
   Latin.char () ':'
   Latin.skipWhile (\c -> c == ' ' || c == '\t')
-  -- Header name allows vchar, space, and tab.
+  -- Header value allows vchar, space, and tab.
   value0 <- Parser.takeWhile $ \c ->
     (c >= 0x20 && c <= 0x7e)
-      || (c == 0x09)
+      || (c == HorizontalTab)
   Latin.char2 () '\r' '\n'
   -- We only need to trim the end because the leading spaces and tab
   -- were already skipped.
