Welcome
Danger helps you to protect your apps from bad actors, validate user info, and detect repeat signups or credential sharing.
- Validate user details on signup: Check email deliverability, disposable domains, phone number reachability, parse addresses, and much more. Also provides country and timezone.
- Plug and play risk protection: Ready made smart rules help you to protect your app from risks such as remote geolocation (block countries), automated bots, anonymous users (VPN, Tor, etc.), throwaway email addresses, and more.
- Act on repeat signups or credential sharing: Danger can link events to the same person, even when they attempt evasions such as incognito mode, clearing cookies, or using VPNs. Danger will show you the person behind the signup.
- Dashboard: For reviewing events, configuring rules, and one-click allowlisting of safe emails or IP addresses.
You can learn more about Danger on the main website.
New here?
The fastest way to start is to follow our getting started tutorial. This will walk you through implementing Danger and explain all the essentials.
Need something specific?
If you have specific questions on how to use the Danger platform, you may want to start with either the API reference or our guides.
The API reference has examples and a detailed description of all the properties and status codes for the event API.
The guides section aims to focus on particular topics to help with troubleshooting and improving your understanding of how Danger works.
⏱️ 5 minute quick start
Very short on time? Implement Danger in 5 minutes. Make sure you've created an account and added a site.
Copy the ready-to-use widget code directly from the dashboard into your site's signup form. It looks like this:
<!-- Insert this code between the <form></form> tags -->
<script src="https://js.usedanger.com/v1/api.js" async defer></script>
<input type="hidden" name="danger-bundle" data-sitekey="<INSERT_SITE_KEY>">
Does your site use a Content Security Policy (CSP)?
You'll need to add some Danger domains to allow the widget to work:
Directive | Add the domain |
---|---|
script-src | js.usedanger.com |
connect-src | *.dgr-t.com |
You should now turn your attention to the server. Check you are now receiving a danger-bundle
field alongside the regular fields on the form. Go ahead and send it to the event API along with the person's details and the secret_key
that you can also copy from the dashboard.
- cURL
- Ruby
- Node
- PHP
- Python
- Go
- .NET
- Java
curl 'https://app.usedanger.com/api/v1/event' \
-H 'Content-Type: application/json' \
--data '{
"type": "new_user",
"email": "[email protected]",
"bundle": "<INSERT_BUNDLE>",
"ip": "<INSERT_REMOTE_IP>",
"secret_key": "<INSERT_SECRET_KEY>"
}'
require 'net/http'
require 'json'
uri = URI('https://app.usedanger.com/api/v1/event')
req = Net::HTTP::Post.new(uri)
req.content_type = 'application/json'
req.body = {
'type' => 'new_user',
'email' => '[email protected]',
'bundle' => '<INSERT_BUNDLE>',
'ip' => '<INSERT_REMOTE_IP>',
'secret_key' => '<INSERT_SECRET_KEY>'
}.to_json
req_options = {
use_ssl: uri.scheme == 'https'
}
res = Net::HTTP.start(uri.hostname, uri.port, req_options) do |http|
http.request(req)
end
import fetch from 'node-fetch';
fetch('https://app.usedanger.com/api/v1/event', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
'type': 'new_user',
'email': '[email protected]',
'bundle': '<INSERT_BUNDLE>',
'ip': '<INSERT_REMOTE_IP>',
'secret_key': '<INSERT_SECRET_KEY>'
})
});
<?php
require 'vendor/autoload.php';
use GuzzleHttp\Client;
$client = new Client();
$response = $client->post('https://app.usedanger.com/api/v1/event', [
'headers' => [
'Content-Type' => 'application/json'
],
'json' => [
'type' => 'new_user',
'email' => '[email protected]',
'bundle' => '<INSERT_BUNDLE>',
'ip' => '<INSERT_REMOTE_IP>',
'secret_key' => '<INSERT_SECRET_KEY>'
]
]);
import requests
headers = {
'Content-Type': 'application/json',
}
json_data = {
'type': 'new_user',
'email': '[email protected]',
'bundle': '<INSERT_BUNDLE>',
'ip': '<INSERT_REMOTE_IP>',
'secret_key': '<INSERT_SECRET_KEY>',
}
response = requests.post('https://app.usedanger.com/api/v1/event', headers=headers, json=json_data)
package main
import (
"fmt"
"io"
"log"
"net/http"
"strings"
)
func main() {
client := &http.Client{}
var data = strings.NewReader(`{
"type": "new_user",
"email": "[email protected]",
"bundle": "<INSERT_BUNDLE>",
"ip": "<INSERT_REMOTE_IP>",
"secret_key": "<INSERT_SECRET_KEY>"
}`)
req, err := http.NewRequest("POST", "https://app.usedanger.com/api/v1/event", data)
if err != nil {
log.Fatal(err)
}
req.Header.Set("Content-Type", "application/json")
resp, err := client.Do(req)
if err != nil {
log.Fatal(err)
}
defer resp.Body.Close()
bodyText, err := io.ReadAll(resp.Body)
if err != nil {
log.Fatal(err)
}
fmt.Printf("%s\n", bodyText)
}
using System.Net.Http;
using System.Net.Http.Headers;
HttpClient client = new HttpClient();
HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Post, "https://app.usedanger.com/api/v1/event");
request.Content = new StringContent("{ \"type\": \"new_user\", \"email\": \"[email protected]\", \"bundle\": \"<INSERT_BUNDLE>\", \"ip\": \"<INSERT_REMOTE_IP>\", \"secret_key\": \"<INSERT_SECRET_KEY>\" }");
request.Content.Headers.ContentType = new MediaTypeHeaderValue("application/json");
HttpResponseMessage response = await client.SendAsync(request);
response.EnsureSuccessStatusCode();
string responseBody = await response.Content.ReadAsStringAsync();
import java.io.IOException;
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpRequest.BodyPublishers;
import java.net.http.HttpResponse;
HttpClient client = HttpClient.newHttpClient();
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create("https://app.usedanger.com/api/v1/event"))
.POST(BodyPublishers.ofString("{ \"type\": \"new_user\", \"email\": \"[email protected]\", \"bundle\": \"<INSERT_BUNDLE>\", \"ip\": \"<INSERT_REMOTE_IP>\", \"secret_key\": \"<INSERT_SECRET_KEY>\" }"))
.setHeader("Content-Type", "application/json")
.build();
HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString());
You should get back a 200
status response with your first event result:
{
"id": "d4bhu2b1",
"url": "https://app.usedanger.com/app/events/d4bhu2b1",
"external_id": null,
"outcome": "allow",
"rules": [],
"device": {
"success": true,
"integrity_ip_match": true,
"integrity_unique": true,
"integrity_unexpired": true,
"fingerprint": "9a78264e64e22174240b666e8408b4b9",
"ja3": "40b666e848b4b99a78264e6e22179742",
"ip": "204.158.96.40",
"ipv6": "2001:db8:3333:4444:5555:6666:7777:8888",
"bot": false,
"browser": "Safari",
"private_browsing": true,
"ua": "Mozilla/5.0 (iPhone; CPU iPhone OS 17_1_1 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.1.1 Mobile/15E148 Safari/604.1",
"ua_os_family": "iOS",
"ua_os_version": "17.1.1",
"ua_device_type": "mobile",
"ua_device_brand": "Apple",
"ua_device_family": "iPhone",
"ua_device_model": "iPhone",
"country_name": "United States",
"country_code": "US",
"timezone": "America/Chicago"
},
"email": {
"success": true,
"valid": true,
"email": "[email protected]",
"local_part": "dan",
"normalized_email": "[email protected]",
"normalized_local_part": "dan",
"domain": "example.com",
"domain_tld": "com",
"domain_tld_high_risk": false,
"domain_registrar": "MarkMonitor, Inc.",
"domain_age": 10359,
"digits_count": 0,
"name_match": true,
"free": true,
"disposable": false,
"business": false,
"breaches": {
"success": true,
"found_count": 10,
"found": [
"CafePress",
"PDL",
"Lastfm",
...
]
},
"registered": {
"total_count": 20,
"success_count": 16,
"success_ratio": 0.8,
"found_count": 4,
"found_ratio": 0.25,
"apple": true,
"disney": false,
"spotify": false,
...
}
},
"ip": {
"success": true,
"in_country": true,
"ip": "204.158.96.40",
"isp": "Comcast Cable Communications LLC",
"organization": "Comcast Cable Communications LLC",
"carrier": "Verizon",
"type": "business",
"city": "Minneapolis",
"state": "Minnesota",
"country_name": "United States",
"country_code": "US",
"postal_code": "55400",
"timezone": "America/Chicago",
"latitude": 40.9255,
"longitude": -89.3034,
"vpn": true,
"relay": false,
"proxy": false,
"tor": false,
"hosting": false,
"threat": false,
"bogon": false
},
"linked": {
"events_all_matched": 0,
"events_all_possible": 0,
"events_all_total": 0,
"events_allowed_matched": 0,
"events_allowed_possible": 0,
"events_allowed_total": 0,
"events_first_seen": null
},
"proximity": {
"device_ip_timezone_match": true,
"address_ip_distance": null,
"address_ip_country_match": null,
"address_phone_country_match": null
}
}
Set your application to respond to a block
or block_review
outcome by preventing the flow from continuing.
🥳 That's the basics done. We recommend you now review the full getting started guide to understand how to strengthen the implementation.