Skip to content

Instantly share code, notes, and snippets.

@bpbradley
Last active October 4, 2025 15:34
Show Gist options
  • Select an option

  • Save bpbradley/6628f7c7486b46dfeefaa95a83373f01 to your computer and use it in GitHub Desktop.

Select an option

Save bpbradley/6628f7c7486b46dfeefaa95a83373f01 to your computer and use it in GitHub Desktop.
Crowdsec Discord Notification

Crowdsec Discord Notifications

Generates a detailed notification via discord when crowdsec makes a ban decision.

The notification includes details about the malicous IP, including geolocation, and will present a static map image of the approximate location in the notification.

It also includes all the target uris, which is very helpful when debugging false positives, so that you can better create whitelists / parsers.

crowdsec

Requirements

  1. Need an API Key from Geoapify, it was the only service I could find with a free tier static maps API. Pass this API key to crowdsec via environment variable with GEOAPIFY_API_KEY: ${GEOAPIFY_API_KEY} (refer to sample compose.yaml)
  2. Optional: For more advanced statistics like Malicousness Score and more detailed geolocation, you will need access to the Crowdsec CTI Smoke database. You can get access to this for free, up to 30 queries/day. You need to generate a CTI API Key on the Crowdsec Console. Once you have your API Key, you will need to configure crowdsec to use it. An example entry in config.yaml (in this case you would also need to set the env variable CTI_API_KEY, or you would manually paste the key in the config file)
api:
  cti:
    key: ${CTI_API_KEY}
    cache_timeout: 60m
    cache_size: 50
    enabled: true
    log_level: debug
  1. Need to create a discord webhook, and take note of the webhook ID and token. The url will be of the form https://discord.com/api/webhooks/${DISCORD_WEBHOOK_ID}/${DISCORD_WEBHOOK_TOKEN} Pass these to crowdsec as environment variables as well, again refer to the sample compose.yaml
environment:
  GEOAPIFY_API_KEY: ${GEOAPIFY_API_KEY}
  DISCORD_WEBHOOK_ID: ${DISCORD_WEBHOOK_ID}
  DISCORD_WEBHOOK_TOKEN: ${DISCORD_WEBHOOK_TOKEN}
  # If using CTI API
  CTI_API_KEY: ${CTI_API_KEY}
  1. Map the notification file (discord.yaml) to crowdsec at /etc/crowdsec/notifications/discord.yaml. Again rever to compose.yaml
volumes:
  - ./notifications/discord.yaml:/etc/crowdsec/notifications/discord.yaml
  1. Don't forget to create a .env file with the contents of the env variables
GEOAPIFY_API_KEY=some-api-key
DISCORD_WEBHOOK_ID=some-webhook-id
DISCORD_WEBHOOK_TOKEN=some-webhook-token
# If using CTI API
CTI_API_KEY=api-key-from-crowdsec-console
  1. Update the mapped profiles.yaml to include the discord notifier, here is an example
name: default_ip_remediation
filters:
 - Alert.Remediation == true && Alert.GetScope() == "Ip"
decisions:
 - type: ban
   duration: 168h
notifications:
#   - slack_default  # Set the webhook in /etc/crowdsec/notifications/slack.yaml before enabling this.
#   - splunk_default # Set the splunk url and token in /etc/crowdsec/notifications/splunk.yaml before enabling this.
#   - http_default   # Set the required http parameters in /etc/crowdsec/notifications/http.yaml before enabling this.
# - email_default  # Set the required email parameters in /etc/crowdsec/notifications/email.yaml before enabling this.
 - discord
on_success: break
  1. Test by manually banning an IP and it should generate a notification docker exec crowdsec cscli decisions add --ip 192.168.1.10 -d 10m
# Just a sample to show usage. Refer to README.md
services:
crowdsec:
image: crowdsecurity/crowdsec:v1.6.5@sha256:fdb487e130095709c1b1c5ba2bd2b461e6715676076e5f774230dcf47d366f76
container_name: crowdsec
environment:
GID: ${GID-1000}
COLLECTIONS: crowdsecurity/linux crowdsecurity/traefik
# Get an API Key from Geoapify. Only service I could get a free tier for static maps
GEOAPIFY_API_KEY: ${GEOAPIFY_API_KEY}
# Details of Discord webhook. Check your discord webhook url, it will be of the form
# https://discord.com/api/webhooks/${DISCORD_WEBHOOK_ID}/${DISCORD_WEBHOOK_TOKEN}
DISCORD_WEBHOOK_ID: ${DISCORD_WEBHOOK_ID}
DISCORD_WEBHOOK_TOKEN: ${DISCORD_WEBHOOK_TOKEN}
TZ: ${TZ}
# Get an API Key for the Crowdsec CTI API if you want Malicousness score and City details
CTI_API_KEY: ${CTI_API_KEY}
volumes:
- ./config/acquis.yaml:/etc/crowdsec/acquis.yaml
- ./config/profiles.yaml:/etc/crowdsec/profiles.yaml
- ./config/config.yaml:/etc/crowdsec/config.yaml
# Make sure to map discord.yaml to /etc/crowdsec/notifications/discord.yaml
- ./notifications/discord.yaml:/etc/crowdsec/notifications/discord.yaml
- crowdsec-db:/var/lib/crowdsec/data/
- crowdsec-config:/etc/crowdsec/
- /var/log/traefik:/var/log/traefik/:ro
networks:
- crowdsec
security_opt:
- no-new-privileges:true
restart: unless-stopped
networks:
crowdsec:
external: true
volumes:
crowdsec-db: null
crowdsec-config: null
---
#
# /etc/crowdsec/notifications/discord.yaml
#
type: http
name: discord
log_level: info
format: |
{
"embeds": [
{
{{range . -}}
{{$alert := . -}}
{{range .Decisions -}}
{{- $cti := .Value | CrowdsecCTI -}}
"timestamp": "{{$alert.StartAt}}",
"title": "Crowdsec Alert",
"color": 16711680,
"description": "Potential threat detected. View details in [Crowdsec Console](<https://app.crowdsec.net/cti/{{.Value}}>)",
"url": "https://app.crowdsec.net/cti/{{.Value}}",
{{if $alert.Source.Cn -}}
"image": {
"url": "https://maps.geoapify.com/v1/staticmap?style=osm-bright-grey&width=600&height=400&center=lonlat:{{$alert.Source.Longitude}},{{$alert.Source.Latitude}}&zoom=8.1848&marker=lonlat:{{$alert.Source.Longitude}},{{$alert.Source.Latitude}};type:awesome;color:%23655e90;size:large;icon:industry|lonlat:{{$alert.Source.Longitude}},{{$alert.Source.Latitude}};type:material;color:%23ff3421;icontype:awesome&scaleFactor=2&apiKey={{env "GEOAPIFY_API_KEY"}}"
},
{{end}}
"fields": [
{
"name": "Scenario",
"value": "`{{ .Scenario }}`",
"inline": "true"
},
{
"name": "IP",
"value": "[{{.Value}}](<https://www.whois.com/whois/{{.Value}}>)",
"inline": "true"
},
{
"name": "Ban Duration",
"value": "{{.Duration}}",
"inline": "true"
},
{{if $alert.Source.Cn -}}
{
"name": "Country",
"value": "{{$alert.Source.Cn}} :flag_{{ $alert.Source.Cn | lower }}:",
"inline": "true"
}
{{if $cti.Location.City -}}
,
{
"name": "City",
"value": "{{$cti.Location.City}}",
"inline": "true"
},
{
"name": "Maliciousness",
"value": "{{mulf $cti.GetMaliciousnessScore 100 | floor}} %",
"inline": "true"
}
{{end}}
{{end}}
{{if not $alert.Source.Cn -}}
{
"name": "Location",
"value": "Unknown :pirate_flag:"
}
{{end}}
{{end -}}
{{end -}}
{{range . -}}
{{$alert := . -}}
{{range .Meta -}}
,{
"name": "{{.Key}}",
"value": "{{ (splitList "," (.Value | replace "\"" "`" | replace "[" "" |replace "]" "")) | join "\\n"}}"
}
{{end -}}
{{end -}}
]
}
]
}
url: https://discord.com/api/webhooks/${DISCORD_WEBHOOK_ID}/${DISCORD_WEBHOOK_TOKEN}
method: POST
headers:
Content-Type: application/json
name: default_ip_remediation
#debug: true
filters:
- Alert.Remediation == true && Alert.GetScope() == "Ip"
decisions:
- type: ban
duration: 168h
notifications:
# - slack_default # Set the webhook in /etc/crowdsec/notifications/slack.yaml before enabling this.
# - splunk_default # Set the splunk url and token in /etc/crowdsec/notifications/splunk.yaml before enabling this.
# - http_default # Set the required http parameters in /etc/crowdsec/notifications/http.yaml before enabling this.
# - email_default # Set the required email parameters in /etc/crowdsec/notifications/email.yaml before enabling this.
- discord
on_success: break
@DKT69
Copy link

DKT69 commented May 16, 2025

hi, the map not showing in discord notification.

image

@bpbradley
Copy link
Author

hi, the map not showing in discord notification.

image

Strange, looks like you have the CTI API setup, so it isn't that. But it appears it is also not showing IP or country. Perhaps there is an issue with the geolocation. And you are sure you setup the maps API correctly? And passed in your API key in the way shown in the instructions? I am not sure, as others have followed the instructions and it works for them.

@DKT69
Copy link

DKT69 commented May 16, 2025

hi, the map not showing in discord notification.
image

Strange, looks like you have the CTI API setup, so it isn't that. But it appears it is also not showing IP or country. Perhaps there is an issue with the geolocation. And you are sure you setup the maps API correctly? And passed in your API key in the way shown in the instructions? I am not sure, as others have followed the instructions and it works for them.

i tried remove CTI api from config.yaml but it still not show the map :(

image

@bpbradley
Copy link
Author

hi, the map not showing in discord notification.
image

Strange, looks like you have the CTI API setup, so it isn't that. But it appears it is also not showing IP or country. Perhaps there is an issue with the geolocation. And you are sure you setup the maps API correctly? And passed in your API key in the way shown in the instructions? I am not sure, as others have followed the instructions and it works for them.

i tried remove CTI api from config.yaml but it still not show the map :(

image

Yeah, it wasn't the CTI API as that was correct (if it wasn't you won't get a malicousness score).

The most likely issue is that the API for geoapify isn't correct. Shell into your crowdsec environment and see if you can check that the geoapify api key is loaded into the environment as needed. for example env | grep GEOAPIFY_API_KEY should return something like GEOAPIFY_API_KEY=your-key-here

@bpbradley
Copy link
Author

And if this is correct and still not working, double check your api key is actually working. This is working for me, and it should be what you are getting based on that IP address.

https://maps.geoapify.com/v1/staticmap?style=osm-bright-grey&width=600&height=400&center=lonlat:101.6852,3.1408&zoom=8.1848&marker=lonlat:101.6852,3.1408;type:awesome;color:%23655e90;size:large;icon:industry|lonlat:101.6852,3.1408;type:material;color:%23ff3421;icontype:awesome&scaleFactor=2&apiKey=YOURAPIKEYHERE

Paste that link into your browser, replacing your apikey at the end with your actual api key from Geoapify. It should give you the exact map that discord would be attaching there. If it doesn't, something is wrong with the account setup or API key.

@DKT69
Copy link

DKT69 commented May 16, 2025

hmmm.. i direct paste the link you give on discord with my API key that work. but using crowdsec discord notification still not sending the map. i have no idea

@bpbradley
Copy link
Author

hmmm.. i direct paste the link you give on discord with my API key that work. but using crowdsec discord notification still not sending the map. i have no idea

Did you check the output of env from my previous comment?

@DKT69
Copy link

DKT69 commented May 16, 2025

hmmm.. i direct paste the link you give on discord with my API key that work. but using crowdsec discord notification still not sending the map. i have no idea

Did you check the output of env from my previous comment?

no i dont use .env, i using direct key in discord.yaml for geoapify API key and discord webhook.

@bpbradley
Copy link
Author

bpbradley commented May 16, 2025

hmmm.. i direct paste the link you give on discord with my API key that work. but using crowdsec discord notification still not sending the map. i have no idea

Did you check the output of env from my previous comment?

no i dont use .env, i using direct key in discord.yaml for geoapify API key and discord webhook.

This may be the source of the issue. The formatting for the templating is pretty finicky so if you edited it you have to be careful that there are no differences in the formatting.

You can paste your file here with the keys redacted if you want and I can double check it later

@DKT69
Copy link

DKT69 commented May 16, 2025

hmmm.. i direct paste the link you give on discord with my API key that work. but using crowdsec discord notification still not sending the map. i have no idea

Did you check the output of env from my previous comment?

no i dont use .env, i using direct key in discord.yaml for geoapify API key and discord webhook.

This may be the source of the issue. The formatting for the templating is pretty finicky so if you edited it you have to be careful that there are no differences in the formatting.

You can paste your file here with the keys redacted if you want and I can double check it later

for sure. thanks for help buddy. i appreciate it.

@DKT69
Copy link

DKT69 commented Jul 16, 2025

hi, do you know how to add TARGET FQDN inside notification?

@bpbradley
Copy link
Author

All this has access to is fields from within the Alert Context and fields we can query with the CTI API. At a quick glance, I am not sure there is anything there for the target FQDN. https://docs.crowdsec.net/docs/next/log_processor/alert_context/intro/

You can ask in the crowdsec discord, they are pretty helpful for questions like this.

@DKT69
Copy link

DKT69 commented Jul 16, 2025

its okay, found it. thanks
image

@bpbradley
Copy link
Author

its okay, found it. thanks image

Great, do you mind sharing the code / field? I may add it to the gist for others to benefit from too.

@DKT69
Copy link

DKT69 commented Jul 16, 2025

    "fields": [
          {
            "name": "Scenario",
            "value": "{{.Scenario}}",
            "inline": "false"
          },
          {
            "name": "IP",
            "value": "[{{.Value}}](<https://www.whois.com/whois/{{.Value}}>)",
            "inline": "false"
          },
          {
            "name": "Ban Duration",
            "value": "{{.Duration}}",
            "inline": "false"
          },
          {{if $alert.Source.Cn -}}
          { 
            "name": "Country",
            "value": "**{{$alert.Source.Cn}}** :flag_{{ $alert.Source.Cn | lower }}:",
            "inline": "false"
          }
          {{if $cti.Location.City -}}
          ,
          { 
            "name": "City",
            "value": "**{{$cti.Location.City}}**",
            "inline": "false"
          },
          { 
            "name": "Maliciousness",
            "value": "{{mulf $cti.GetMaliciousnessScore 100 | floor}} %",
            "inline": "false"
          },
          {{end}}
          {{end}}
          {{if not $alert.Source.Cn -}}
          { 
            "name": "Location",
            "value": "Unknown :pirate_flag:"
          }
          {{end}}
          {{end -}}
          {{end -}}
          {{range . -}}
          {{$alert := . -}}
          {{if GetMeta $alert "target_fqdn" -}}
          ,{
            "name": "target_url",
            "value": "{{range (GetMeta $alert "target_fqdn" | uniq) -}}`{{.}}`\n{{ end -}}"
          }
          {{end}}
          {{range .Meta -}}
            ,{
            "name": "{{.Key}}",
            "value": "{{ (splitList "," (.Value | replace "\"" "`" | replace "[" "" |replace "]" "")) | join "\\n"}}"
          } 
          {{end -}}
          {{end -}}

@bpbradley
Copy link
Author

Thank you! I will look into it for myself in the future. I was thinking about a bit of an update soon anyway.

@serafearz
Copy link

Hi, is there any possibility to add the TARGETED IP in the notification?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment