diff --git a/.readthedocs.yaml b/.readthedocs.yaml
new file mode 100644
index 000000000..c1545635d
--- /dev/null
+++ b/.readthedocs.yaml
@@ -0,0 +1,9 @@
+version: 2
+
+build:
+ os: ubuntu-22.04
+ tools:
+ python: 3
+
+mkdocs:
+ configuration: mkdocs.yml
diff --git a/docs/rotating-keys.md b/docs/rotating-keys.md
new file mode 100644
index 000000000..dafe912b1
--- /dev/null
+++ b/docs/rotating-keys.md
@@ -0,0 +1,211 @@
+# Rotating Keys
+
+Key rotation consists in retiring and replacing cryptographic keys with new ones.
+Performing that operation on a regular basis is an industry standard.
+
+## Why should I rotate my keys?
+
+Rotating keys allows us to:
+
+1. Limit the number of tokens signed with the same key, helping the prevention of attacks enabled by cryptanalysis
+2. Adopt other algorithms or stronger keys
+3. Limit the impact of eventual compromised keys
+
+## The challenges
+
+After rotating keys, apps will likely receive requests with tokens issues with the previous key.
+If the key rotation of an app is done with a "hard cut", requests with non-expired tokens issued with the old key **will fail**!
+
+Imagine if you were the user who logged in just before a key rotation on that kind of app, you'd probably have to log in again!
+
+That's rather frustrating, right!?
+
+## Preventing issues
+
+It's possible to handle key rotation in a smoother way by leveraging the `SignedWithOneInSet` validation constraint!
+
+Say your application uses the symmetric algorithm `HS256` with a not so secure key to issue tokens:
+
+```php
+issue(
+ new Signer\Hmac\Sha256(),
+ InMemory::plainText(
+ 'a-very-long-and-secure-key-that-should-actually-be-something-else'
+ ),
+ static fn (Builder $builder): Builder => $builder
+ ->issuedBy('https://api.my-awesome-app.io')
+ ->permittedFor('https://client-app.io')
+);
+```
+
+!!! Sample
+ Here's a token issued with the code above, if you want to test the script locally:
+
+
+ Sample token
+
+ // line breaks added for readability
+ eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9
+ .eyJpYXQiOjE2OTkxMzE5NjEsIm5iZiI6MTY5OTEzMTk2MSwiZXhwIjoxNjk5MTMyMjYxLCJpc3MiOiJ
+ odHRwczovL2FwaS5teS1hd2Vzb21lLWFwcC5pbyIsImF1ZCI6Imh0dHBzOi8vY2xpZW50LWFwcC5pbyJ9
+ .IA9S0n8Q2O97lyR8KczVE8g-hxbbH6_TfJS-JWTQR4c
+
+
+Your parsing logic (with validations) look like:
+
+```php
+parse($jwt, ...$validationConstraints);
+```
+
+### Performing a backwards compatible rotation
+
+Now Imagine that you want to adopt the new `BLAKE2B` symmetric algorithm.
+
+These are the changes to your issuing logic:
+
+```diff
+ issue(
+- new Signer\Hmac\Sha256(),
++ new Signer\Blake2b(),
+- InMemory::plainText(
+- 'a-very-long-and-secure-key-that-should-actually-be-something-else'
++ InMemory::base64Encoded(
++ 'GOu4rLyVCBxmxP+sbniU68ojAja5PkRdvv7vNvBCqDQ='
+ ),
+ static fn (Builder $builder): Builder => $builder
+ ->issuedBy('https://api.my-awesome-app.io')
+ ->permittedFor('https://client-app.io')
+ );
+```
+
+!!! Sample
+ Here's a token issued with the code above, if you want to test the script locally:
+
+
+ Sample token
+
+ // line breaks added for readability
+ eyJ0eXAiOiJKV1QiLCJhbGciOiJCTEFLRTJCIn0
+ .eyJpYXQiOjE2OTkxMzE5NjEsIm5iZiI6MTY5OTEzMTk2MSwiZXhwIjoxNjk5MTMyMjYxLCJpc3Mi
+ OiJodHRwczovL2FwaS5teS1hd2Vzb21lLWFwcC5pbyIsImF1ZCI6Imh0dHBzOi8vY2xpZW50LWFwc
+ C5pbyJ9.bD67s8IXpAJiBTIZn1et_M5WSS7kfmuNiacNRz5lArQ
+
+
+So far, nothing different that a normal rotation.
+
+Now check the changes on the parsing and validation logic:
+
+```diff
+ parse($jwt, ...$validationConstraints);
+```
+
+Now the application is able to accept non-expired tokens issued with either old and new keys!
+
+!!! Important
+ The order of `SignedWith` constraints given to `SignedWithOneInSet` does matter, and it's recommended to leave older keys at the end of the list.
diff --git a/mkdocs.yml b/mkdocs.yml
index 8dbba334f..1a3ef8581 100644
--- a/mkdocs.yml
+++ b/mkdocs.yml
@@ -14,6 +14,7 @@ nav:
- 'configuration.md'
- Guides:
- 'extending-the-library.md'
+ - 'rotating-keys.md'
- 'upgrading.md'
markdown_extensions: