I did something similar a while back but using all the invisible characters to encode extra data into telegram messages for metadata storage

https://github.com/sixhobbits/unisteg

I was very confused why this would be useful for Telegram messages, but the Why? part of the readme makes perfect sense. Great workaround for a stupid limitation!

I had fun writing a Racket version:

    #lang racket/base

    (require net/base64
             threading)
    
    (define FIRST-INVISIBLE-CHAR 917760)
    
    (define (invis-encode str)
      (list->string
       (for/list ([c (in-list (string->list str))]
                  #:do [(define cnum (char->integer c))]
                  #:when (<= cnum 127))
         (integer->char (+ cnum FIRST-INVISIBLE-CHAR)))))

    (define (invis-decode str)
      (list->string
       (for/list ([c (in-list (string->list str))]
                  #:do [(define plaintxt-c (- (char->integer c) FIRST-INVISIBLE-CHAR))]
                  #:when (> plaintxt-c 0))
         (integer->char plaintxt-c))))

    (define (hide secret plain)
      (~> (string->bytes/utf-8 secret)
          (base64-encode #"")         ; use #"" vs #"\r\n" to prevent line-wrapping
          (bytes->string/utf-8)
          (invis-encode)
          (string-append plain _)))

    (define (unhide ciphertext)
      (~> (invis-decode ciphertext)
          (string->bytes/utf-8)
          (base64-decode)
          (bytes->string/utf-8)))

    (module+ test
      (require rackunit)
      (define secret "this is a s3cret message. ssh")
      (define plaintext "Hey you, nothing to see here.")
      (define to-share (hide secret plaintext))
    
      (check-equal? (string-length to-share) 69)         ; count of bytes
      (check-equal? (string-grapheme-count to-share) 29) ; 29 actually-visible graphemes
      (check-equal? secret (unhide to-share)))

Threading is done with the wave character ~ in Racket? I can't decide if I hate it or not (am used to Clojure's ->). I think my pinky finger doesn't like ~.

In this case ~> is a macro from a widely used package (https://docs.racket-lang.org/threading/index.html) so if you defined an alias for it (or forked the package) you could use any valid identifier.