diff --git a/docs/extras/forms.md b/docs/extras/forms.md
new file mode 100644
index 00000000..e67a65fb
--- /dev/null
+++ b/docs/extras/forms.md
@@ -0,0 +1,92 @@
+# The Form
+
+This is a special way of sending a form directly using Esmerald. The `Form` like the
+[File](./upload-files.md), inherits from the [Body](./body-fields.md) and applies the special
+`media_type` as `application/x-www-form-urlencoded`.
+
+This also means that you can also use the `Body` directly to send a form with your API by simply
+declaring `Body(media_type="application/x-www-form-urlencoded")`.
+
+The Form is a simple and cleaner shortcut for it.
+
+The simplest way is by importing the `Form` object from Esmerald.
+
+```python hl_lines="7"
+{!> ../docs_src/extras/form/form_object.py !}
+```
+
+You can also import via:
+
+```python
+from esmerald.params import Form
+```
+
+As [explained here](./request-data.md#request-data), the handler is expecting a `data` field declared and from there
+you can pass more details about the form.
+
+
+## Examples
+
+You can send the form in many different formats, for example:
+
+1. [A dictionary](#sending-as-dictionary) - Send as normal dictionary.
+2. [A dataclass](#a-dataclass) - Send as normal dataclass.
+3. [A pydantic dataclass](#pydantic-dataclass) - Send a pydantic dataclass.
+4. [Pydantic model](#pydantic-model) - Send a pydantic BaseModel.
+
+You decide the best format to send. For the following examples, we will be using `httpx` for the
+requests for explanatory purposes.
+
+### Sending as dictionary
+
+```python hl_lines="9 20 23"
+{!> ../docs_src/extras/form/as_dict.py !}
+```
+
+As you can see, we declared the return signature to be `Dict[str, str]` and the `data` payload to
+be a dictionary also `Dict[str, str]`. This way we acn simply send the form as you would normally
+do.
+
+### A dataclass
+
+What if you want to type as a dataclass and return it in your response?
+
+```python hl_lines="15 26 29"
+{!> ../docs_src/extras/form/dataclass.py !}
+```
+
+The way the payload is sent to the API will always be the same no matter what, what is important
+is how you actually type it. In this example, we declared a `User` dataclass with two field
+`name` and `email` and we return exactly what we sent back into the response.
+
+
+### Pydantic dataclass
+
+A Pydantic dataclass is the same as a normal python dataclass in the end but with some internal
+extras from Pydantic but for Esmerald, it is the same.
+
+```python hl_lines="14 25 28"
+{!> ../docs_src/extras/form/pydantic_dc.py !}
+```
+
+### Pydantic model
+
+What if we want to type and return as a Pydantic model? Well, it behaves exactly the same as the
+dataclasses.
+
+```python hl_lines="13 24 27"
+{!> ../docs_src/extras/form/model.py !}
+```
+
+## Notes
+
+As you could see from the examples, it is very simple and direct to use the `Form` in Esmerald and
+the returns are simply clean.
+
+### Important
+
+Since `Form` is Pydantic field (sort of), that also means you can specify for instance,
+the other parameters to be evaluated.
+
+You can check [the list of available parameters default](https://docs.pydantic.dev/latest/api/fields/#pydantic.fields.FieldInfo)
+as well.
diff --git a/docs/responses.md b/docs/responses.md
index 8fb0fe88..4c46f494 100644
--- a/docs/responses.md
+++ b/docs/responses.md
@@ -6,6 +6,12 @@ Esmerald having `Starlette` under the hood also means that all available respons
You simply just need to decide which type of response your function will have and let `Esmerald` take care of the rest.
+!!! Tip
+
+ Esmerald automatically understands if you are typing/returning a **dataclass**,
+ a **Pydantic dataclass** or a **Pydantic model** and converts
+ them automatically into a [JSON response](#jsonresponse).
+
## Esmerald responses and the application
The available responses from `Esmerald` are:
@@ -130,6 +136,9 @@ This response returns a `TemplateResponse`.
* **name** - Template name/location. E.g.: `accounts/list.html`.
* **context** - The dict context to be sent to the template.
+* **alternative_template** - Template name/location of an alternative template if the **name** of
+the original is not found.
+E.g.: If `accounts/list.html` is not found `alternative_template="base/list.html"`.
### Redirect
@@ -362,3 +371,24 @@ unique and you might want to return directly a `string`, a `dict`, an `integer`,
```python
{!> ../docs_src/responses/others.py !}
```
+### Example
+
+Below we have a few examples of possible responses recognised by Esmerald automatically.
+
+**Pydantic model**
+
+```python hl_lines="13 24 27"
+{!> ../docs_src/extras/form/model.py !}
+```
+
+**Pydantic dataclass**
+
+```python hl_lines="14 25 28"
+{!> ../docs_src/extras/form/pydantic_dc.py !}
+```
+
+**Python dataclass**
+
+```python hl_lines="15 26 29"
+{!> ../docs_src/extras/form/dataclass.py !}
+```
diff --git a/docs/routing/webhooks.md b/docs/routing/webhooks.md
new file mode 100644
index 00000000..6d1bbae8
--- /dev/null
+++ b/docs/routing/webhooks.md
@@ -0,0 +1,177 @@
+# Webhooks
+
+OpenAPI Webhooks are those cases where you want to tell your API users that your application
+could/should/would call **their** own application, for example, sending a request with specific
+bits of data, usually to **notify** them of some type of event.
+
+This also means that instead of your users sending requests to your APIs, it is your application
+**sendind requests** to their application.
+
+This process is called **webhook**.
+
+## Esmerald webhooks
+
+Esmerald provides a way of declaring these webhooks in the OpenAPI specification. It is very, very
+similar to the way the [Gateway](routes.md#gateway) is declared but **dedicated to webhooks**.
+
+The process usually is that you define in your code, as normal, what is the message that you will
+send, in other words, **the body of the request**.
+
+You also define in some way at which moments your app will send those requests or events.
+
+Your users on the other hand, define some way (web dashboard, for instance) the URL where your
+application should send those requests.
+
+The way the logic how to register the URLs for the webhooks and the code to performs the said
+actions is entirely up to you.
+
+## Documenting Esmerald webhooks with OpenAPI
+
+As mentioned before, the way of doing it is very similar to the way you declare
+[Gateway](routes.md#gateway) but for this purpose, webhooks have a **special dedicated** object or
+objects to do make it happen, the [WebhookGateway](#webhookgateway).
+
+Also, the webhooks **are not *hooked* into the application routing system**, instead, they are
+placed in the `webhooks` list.
+
+```python hl_lines="3"
+from esmerald import Esmerald
+
+app = Esmerald(webhooks=[...])
+```
+
+### WebhookGateway
+
+As the name indicated, the `WebhookGateway` is the main object where you declare the hooks for
+the OpenAPI specification and **unlike the Gateway**, it does not declare a `path` (example, `/event`),
+instead, it only needs to receive the **name** of the action.
+
+Like the Gateway, the **WebhookGateway** also expects a [handler](#handlers) but
+**not the same handler as you usually use for the routes**, a special **webhook handler**.
+
+#### How to import it
+
+You can import them directly:
+
+```python
+from esmerald import WebhookGateway
+```
+
+Or you can use the full path.
+
+```python
+from esmerald.routing.gateways import WebhookGateway
+```
+
+#### Parameters
+
+* **path** - Altough is called path, it corresponds to the **name** of the webhook
+and it does not require a `/` at the beginning of it like a normal path would. E.g.: `subscription`.
+* **handler** - The [handler](#handlers) function of the webhook.
+* **include_in_schema** - Flag if the webhook should be included in the OpenAPI schema.
+* **deprecated** - Flag if the webhook is deprecated.
+
+### Handlers
+
+The handlers for the **webhooks** are pretty much similar to the normal handlers used for routing
+but **dedicated** only to the **WebhookGateway**. The available handlers are:
+
+* **whget** - For the `GET`.
+* **whpost** - For the `POST`.
+* **whput** - For the `PUT`.
+* **whpatch** - For the `PATCH`.
+* **whdelete** - For the `DELETE`.
+* **whead** - For the `HEAD`.
+* **whoptions** - For the `OPTION`.
+* **whtrace** - For the `TRACE`.
+* **whroute** - Used to specificy for which `http verbs` is available. This handler has the special
+`methods` attribute. E,g.:
+
+ ```python
+ from esmerald import whroute
+
+ @whroute(methods=["GET", "POST"])
+ ...
+ ```
+
+As you can already see, the handlers are very similar to the [routing handler](./handlers.md) but
+dedicated to this purpose and **all of them start with `wh`**.
+
+The `wh` at the beginning of each handler means **W**eb**H**ook.
+
+#### How to import them
+
+You can import them directly:
+
+```python
+from esmerald import (
+ whdelete,
+ whead,
+ whget,
+ whoptions,
+ whpatch,
+ whpost,
+ whput,
+ whroute,
+ whtrace
+)
+```
+
+Or via full path.
+
+```python
+from esmerald.routing.webhooks.handlers import (
+ whdelete,
+ whead,
+ whget,
+ whoptions,
+ whpatch,
+ whpost,
+ whput,
+ whroute,
+ whtrace
+)
+```
+
+## An Esmerald application with webhooks
+
+When you create an **Esmerald** application, as mentioned before, there is a `webhooks` attribute
+that you use to define your application `webhooks`, in a similar way you define the `routes`.
+
+```python hl_lines="6 21 16 28"
+{!> ../docs_src/routing/webhooks/example.py !}
+```
+
+Note how the `whpost` and `post` are declared inside the `webhooks` and `routes` respectively,
+**similar but not the same** and how the `whpost` **does not require** the `/` for the path.
+
+The webhooks you define **will end up** in the **OpenAPI** schema automatically.
+
+### Using the APIView to generate webhooks
+
+Since Esmerald supports class based views, that also means you can also use them to generate
+webhooks.
+
+```python
+{!> ../docs_src/routing/webhooks/cbv.py !}
+```
+
+## Important
+
+Notice that with webhooks you are actually not declaring a path (like `/user`). The text you pass
+there is just a `name` or an **identifier** of the webhook (name of the event).
+
+This happens because it is expected that your users would actually define the proper URL path where
+they want to receive the webhook in some way.
+
+## Check out the docs
+
+Let us see how it would look like in the docs if we were declaring the webhooks from the examples.
+
+**First example, no Class Based Views**
+
+
+
+**Second example, with Class Based Views**
+
+
diff --git a/docs_src/extras/form/as_dict.py b/docs_src/extras/form/as_dict.py
new file mode 100644
index 00000000..93897751
--- /dev/null
+++ b/docs_src/extras/form/as_dict.py
@@ -0,0 +1,23 @@
+from typing import Any, Dict
+
+import httpx
+
+from esmerald import Esmerald, Form, Gateway, post
+
+
+@post("/create")
+async def create(data: Dict[str, str] = Form()) -> Dict[str, str]:
+ """
+ Creates a user in the system and does not return anything.
+ Default status_code: 201
+ """
+ return data
+
+
+app = Esmerald(routes=[Gateway(handler=create)])
+
+# Payload example
+data = {"name": "example", "email": "example@esmerald.dev"}
+
+# Send the request
+httpx.post("/create", data=data)
diff --git a/docs_src/extras/form/dataclass.py b/docs_src/extras/form/dataclass.py
new file mode 100644
index 00000000..3b6cc470
--- /dev/null
+++ b/docs_src/extras/form/dataclass.py
@@ -0,0 +1,29 @@
+from dataclasses import dataclass
+
+import httpx
+
+from esmerald import Esmerald, Form, Gateway, post
+
+
+@dataclass
+class User:
+ name: str
+ email: str
+
+
+@post("/create")
+async def create(data: User = Form()) -> User:
+ """
+ Creates a user in the system and does not return anything.
+ Default status_code: 201
+ """
+ return data
+
+
+app = Esmerald(routes=[Gateway(handler=create)])
+
+# Payload example
+data = {"name": "example", "email": "example@esmerald.dev"}
+
+# Send the request
+httpx.post("/create", data=data)
diff --git a/docs_src/extras/form/form_object.py b/docs_src/extras/form/form_object.py
new file mode 100644
index 00000000..21d0b95e
--- /dev/null
+++ b/docs_src/extras/form/form_object.py
@@ -0,0 +1,14 @@
+from typing import Any, Dict
+
+from esmerald import Esmerald, Form, Gateway, post
+
+
+@post("/create")
+async def create_user(data: Dict[str, Any] = Form()) -> None:
+ """
+ Creates a user in the system and does not return anything.
+ Default status_code: 201
+ """
+
+
+app = Esmerald(routes=[Gateway(handler=create_user)])
diff --git a/docs_src/extras/form/model.py b/docs_src/extras/form/model.py
new file mode 100644
index 00000000..6ec775bf
--- /dev/null
+++ b/docs_src/extras/form/model.py
@@ -0,0 +1,27 @@
+import httpx
+from pydantic import BaseModel
+
+from esmerald import Esmerald, Form, Gateway, post
+
+
+class User(BaseModel):
+ name: str
+ email: str
+
+
+@post("/create")
+async def create(data: User = Form()) -> User:
+ """
+ Creates a user in the system and does not return anything.
+ Default status_code: 201
+ """
+ return data
+
+
+app = Esmerald(routes=[Gateway(handler=create)])
+
+# Payload example
+data = {"name": "example", "email": "example@esmerald.dev"}
+
+# Send the request
+httpx.post("/create", data=data)
diff --git a/docs_src/extras/form/pydantic_dc.py b/docs_src/extras/form/pydantic_dc.py
new file mode 100644
index 00000000..7023107a
--- /dev/null
+++ b/docs_src/extras/form/pydantic_dc.py
@@ -0,0 +1,28 @@
+import httpx
+from pydantic.dataclasses import dataclass
+
+from esmerald import Esmerald, Form, Gateway, post
+
+
+@dataclass
+class User:
+ name: str
+ email: str
+
+
+@post("/create")
+async def create(data: User = Form()) -> User:
+ """
+ Creates a user in the system and does not return anything.
+ Default status_code: 201
+ """
+ return data
+
+
+app = Esmerald(routes=[Gateway(handler=create)])
+
+# Payload example
+data = {"name": "example", "email": "example@esmerald.dev"}
+
+# Send the request
+httpx.post("/create", data=data)
diff --git a/docs_src/responses/template.py b/docs_src/responses/template.py
index b47d8945..0b650780 100644
--- a/docs_src/responses/template.py
+++ b/docs_src/responses/template.py
@@ -14,6 +14,7 @@ def home() -> Template:
return Template(
name="my-tem",
context={"user": "me"},
+ alternative_template=...,
)
diff --git a/docs_src/routing/webhooks/cbv.py b/docs_src/routing/webhooks/cbv.py
new file mode 100644
index 00000000..c5728ce7
--- /dev/null
+++ b/docs_src/routing/webhooks/cbv.py
@@ -0,0 +1,34 @@
+from datetime import datetime
+
+from pydantic import BaseModel
+
+from esmerald import APIView, Esmerald, Gateway, post
+from esmerald.routing.gateways import WebhookGateway
+from esmerald.routing.webhooks.handlers import whpost
+
+
+class Payment(BaseModel):
+ is_paid: bool
+ amount: float
+ paid_at: datetime
+
+
+class PaymentWebhook(APIView):
+ @whpost("new-event")
+ async def new_event(self, data: Payment) -> None:
+ ...
+
+ @whpost("payments")
+ async def new_payment(self, data: Payment) -> None:
+ ...
+
+
+@post("/create")
+async def create_payment(data: Payment) -> None:
+ ...
+
+
+app = Esmerald(
+ routes=[Gateway(handler=create_payment)],
+ webhooks=[WebhookGateway(handler=PaymentWebhook)],
+)
diff --git a/docs_src/routing/webhooks/example.py b/docs_src/routing/webhooks/example.py
new file mode 100644
index 00000000..dad83861
--- /dev/null
+++ b/docs_src/routing/webhooks/example.py
@@ -0,0 +1,29 @@
+from datetime import datetime
+
+from pydantic import BaseModel
+
+from esmerald import Esmerald, Gateway, post
+from esmerald.routing.gateways import WebhookGateway
+from esmerald.routing.webhooks.handlers import whpost
+
+
+class Payment(BaseModel):
+ is_paid: bool
+ amount: float
+ paid_at: datetime
+
+
+@whpost("new-event")
+async def new_event(data: Payment) -> None:
+ ...
+
+
+@post("/create")
+async def create_payment(data: Payment) -> None:
+ ...
+
+
+app = Esmerald(
+ routes=[Gateway(handler=create_payment)],
+ webhooks=[WebhookGateway(handler=new_event)],
+)
diff --git a/mkdocs.yml b/mkdocs.yml
index dbe44a28..5682d801 100644
--- a/mkdocs.yml
+++ b/mkdocs.yml
@@ -58,6 +58,8 @@ nav:
- Routes: "routing/routes.md"
- Handlers: "routing/handlers.md"
- APIView: "routing/apiview.md"
+ - OpenAPI Specific:
+ - Webhooks: "routing/webhooks.md"
- Databases:
- Saffier:
- Motivation: "databases/saffier/motivation.md"
@@ -85,6 +87,7 @@ nav:
- Extra, Advanced & Useful:
- Request Data: "extras/request-data.md"
- Upload Files: "extras/upload-files.md"
+ - Form: "extras/forms.md"
- Body: "extras/body-fields.md"
- Headers: "extras/header-fields.md"
- Cookies: "extras/cookie-fields.md"