Files
zitadel/apps/docs/content/guides/integrate/actions/testing-request-signature.mdx
T
cc7a6145f1 chore: rename manager/member to administrator (#11542)
# Which Problems Are Solved

As part of the naming consistency initiative, the terms `Manager`,
`Member` and other similar terms that are used in the context of an
administrator are replaced by `Administrator` in the UI and
documentation.

# How the Problems Are Solved

By replacing the following terms with Administrator / Administrator
Roles in docs, Console UI, and guides.

- Manager
- Add Manager
- Add a Manager
- Administrator (used as a person type)
- Membership
- Member grants

# Additional Changes
minor grammatical edits in the docs

# Additional Context
- Closes https://github.com/zitadel/zitadel/issues/11291

---------

Co-authored-by: Marco A. <[email protected]>
2026-02-11 17:43:06 +01:00

241 lines
8.6 KiB
Plaintext

---
title: Verify Payload Integrity
---
import JSONCode from "./go/json.go";
import JWTCode from "./go/jwt.go";
import JWECode from "./go/jwe.go";
import CodeBlock from "@/components/docusaurus/code-block";
This guide shows you how to verify the integrity of the received data on your target.
There are three options available, which will be demonstrated in the following sections.
The examples are based on the request action, but the same principles apply to other action types as well.
## Prerequisites
Before you start, make sure you have everything set up correctly.
- You need to be at least a ZITADEL [_IAM_OWNER_](/guides/manage/console/administrators)
<Callout type="info">
Note that this guide assumes that ZITADEL is running on the same machine as the target and can be reached via `localhost`.
In case you are using a different setup, you need to adjust the target URL accordingly and will need to make sure that the target is reachable from ZITADEL.
</Callout>
## Payload and Validation Types
ZITADEL supports the following three types of payload in actions:
- **JSON**: The payload is sent as JSON in the request body. Additionally, a signature header is sent to validate the payload.
This is the simplest form of payload and validation and doesn't require any additional calls to validate the payload. It's also the default type,
which will be used if no type is specified.
- **JWT**: The payload is sent as a JSON Web Token (JWT) in the request body. The JWT is signed with the signing key of the instance.
This allows you to validate the payload by verifying the signature of the JWT using the signing key managed though the webkeys endpoint of the ZITADEL instance,
which allows for easier key rotation and management. Additionally, it's the base for the JWE type, in case you need to encrypt the payload for certain use cases.
- **JWE**: The payload is sent as encrypted JSON Web Token (JWE) in the request body. The JWT is additionally encrypted with the public key provided by you.
This allows you to validate and decrypt the payload by verifying the signature of the JWT using the signing key managed though the webkeys endpoint of the ZITADEL instance,
and decrypting the payload using your private key. This is the most secure form of payload and validation, but requires additional setup to provide the public key to ZITADEL.
This type is recommended if the payload contains sensitive information that should not be exposed to any intermediaries.
## Create target
We'll start the endpoint on port '8090' and if we want to use it as webhook, the target can be created as follows:
See [Create a target](/apis/resources/action_service_v2/action-service-create-target) for more detailed information.
Specify the `payloadType` according to the implementation you want to test: `PAYLOAD_TYPE_JSON`, `PAYLOAD_TYPE_JWT`, or `PAYLOAD_TYPE_JWE`.
```shell
curl -L -X POST 'https://${CUSTOM_DOMAIN}/v2/actions/targets' \
-H 'Content-Type: application/json' \
-H 'Accept: application/json' \
-H 'Authorization: Bearer <TOKEN>' \
--data-raw '{
"name": "local webhook",
"restWebhook": {
"interruptOnError": true
},
"endpoint": "http://localhost:8090/webhook",
"timeout": "10s",
"payloadType": "PAYLOAD_TYPE_JSON"
}'
```
Example response after creating the target:
```json
{
"id": "344649040681500814",
"creationDate": "2025-10-31T15:00:36.432595dZ",
"signingKey": "somekey"
}
```
Save the returned ID to set in the execution. If you're intending to use the `PAYLOAD_TYPE_JSON`, additionally store the `signingKey` and use it in the example above.
### JWE Specific Setup
If you are using the `PAYLOAD_TYPE_JWE`, you need to provide a public key to ZITADEL so that it can encrypt the payload.
Create a public/private key pair. You can use the following command to generate an RSA key pair:
```shell
openssl genpkey -algorithm RSA -outform PEM -out private_key.pem -pkeyopt rsa_keygen_bits:2048
openssl rsa -pubout -in private_key.pem -out public_key.pem
```
Then upload the public key to ZITADEL using the following command. Replace `<TargetID>` with the ID of the target you created earlier.
Use the base64 encoded content of the `public_key.pem` file as the value for `publicKey`.
```shell
curl -L -X POST 'https://${CUSTOM_DOMAIN}/v2/actions/targets/<TargetID>/publickeys' \
-H 'Content-Type: application/json' \
-H 'Accept: application/json' \
-H 'Authorization: Bearer <TOKEN>' \
--data-raw '{
"publicKey": "<base64 encoded contents of public_key.pem>"
}'
```
Be sure to also activate the public key for the target using the returned `<KeyID>` from the previous request:
```shell
curl -L -X PUT 'https://${CUSTOM_DOMAIN}/v2/actions/targets/<TargetID>/publickeys/<KeyID>/activate' \
-H 'Content-Type: application/json' \
-H 'Accept: application/json' \
-H 'Authorization: Bearer <TOKEN>'
```
## Set execution
To configure ZITADEL to call the target when an API endpoint is called, you need to set an execution and define the request
condition.
See [Set an execution](/apis/resources/action_service_v2/action-service-set-execution) for more detailed information.
```shell
curl -L -X PUT 'https://${CUSTOM_DOMAIN}/v2/actions/executions' \
-H 'Content-Type: application/json' \
-H 'Accept: application/json' \
-H 'Authorization: Bearer <TOKEN>' \
--data-raw '{
"condition": {
"request": {
"method": "/zitadel.user.v2.UserService/CreateUser"
}
},
"targets": [
"<TargetID returned>"
]
}'
```
## Start example target
To test the actions feature, you need to create a target that will be called when an API endpoint is called.
You will need to implement a listener that can receive HTTP requests.
For this example, we will use a simple Go HTTP server that will print the received request to standard output.
As mentioned before, this validation can and should be applied to any target implementation.
<Tabs>
<Tab value="JSON">
<CodeBlock language="go">{JSONCode}</CodeBlock>
</Tab>
<Tab value="JWT">
<CodeBlock language="go">{JWTCode}</CodeBlock>
</Tab>
<Tab value="JWE">
<CodeBlock language="go">{JWECode}</CodeBlock>
</Tab>
</Tabs>
<Callout type="info">
The example above runs only on your local machine (`localhost`).
To test it with Zitadel, you must make your listener reachable from the internet.
You can do this by using **Webhook.site** (see [Creating a Listener with Webhook.site](./webhook-site-setup)).
</Callout>
## Example call
Now that you have set up the target and execution, you can test it by creating a user through the Management Console UI or
by calling the ZITADEL API to create a user.
```shell
curl -L -X POST 'https://${CUSTOM_DOMAIN}/v2/users/new' \
-H 'Content-Type: application/json' \
-H 'Accept: application/json' \
-H 'Authorization: Bearer <TOKEN>' \
--data-raw '{
"organizationId": "344648897353810062",
"human":
{
"profile":
{
"givenName": "Minnie",
"familyName": "Mouse",
"nickName": "Mini",
"displayName": "Minnie Mouse",
"preferredLanguage": "en",
"gender": "GENDER_FEMALE"
},
"email":
{
"email": "[email protected]"
}
}
}'
```
Your server should now print out something like the following. Check out
the [Sent information Request](./usage#sent-information-request) payload description.
```json
{
"fullMethod": "/zitadel.user.v2.UserService/CreateUser",
"instanceID": "344648897353744526",
"orgID": "344648897353810062",
"projectID": "344648897353875598",
"userID": "344648897354465422",
"request":
{
"organizationId": "344648897353810062",
"human":
{
"profile":
{
"givenName": "Minnie",
"familyName": "Mouse",
"nickName": "Mini",
"displayName": "Minnie Mouse",
"preferredLanguage": "en",
"gender": "GENDER_FEMALE"
},
"email":
{
"email": "[email protected]"
}
}
},
"headers":
{
"Content-Type":
[
"application/grpc"
],
"Host":
[
"localhost:8080"
],
"X-Forwarded-For":
[
"::1"
],
"X-Forwarded-Host":
[
"localhost:8080"
]
}
}
```
## Conclusion
You have successfully set up a target and verified the payload integrity for request actions using your preferred payload type.
You can now extend this setup to other action types and integrate it into your workflows as needed.
Selecting the appropriate payload type ensures that your data is transmitted securely and can be validated effectively on the receiving end.
Find more information about the actions feature in the [API documentation](/concepts/features/actions_v2).