Docs menu
Guides
API reference
Java integration
Screen sign-ups from Java 11 or later with java.net.http (the webhook receiver needs Java 17 for HexFormat). These are the console's snippets; there is no Java package.
Last updated
Screen a sign-up
Call /siteverify with the browser token before you create the account, and fall back to /verify when there is no token. The constants at the top choose strict mode and fail-closed behaviour. See the quickstart for the browser script.
// Java 11+. Minimal JSON handling without a library; use Jackson/Gson if you have one.
// Calls https://api.inlineshield.io/api/v1/siteverify (and https://api.inlineshield.io/api/v1/verify when there is no token).
static final HttpClient INLINESHIELD_HTTP = HttpClient.newBuilder().connectTimeout(Duration.ofSeconds(3)).build();
// Returns the body of a 2xx answer. Throws IOException on transport error, timeout, any non-2xx
// (402 QUOTA_EXCEEDED, 429, 5xx, 400 ...), non-JSON or QUOTA_EXCEEDED (= InlineShield unavailable),
// and SecurityException on 401 (bad API key: always rejected).
static String inlineshieldPost(String path, String body) throws IOException, InterruptedException {
HttpRequest vr = HttpRequest.newBuilder(URI.create("https://api.inlineshield.io" + path))
.timeout(Duration.ofSeconds(3))
.header("X-Api-Key", System.getenv("INLINESHIELD_API_KEY"))
.header("Content-Type", "application/json")
.POST(HttpRequest.BodyPublishers.ofString(body))
.build();
HttpResponse<String> r = INLINESHIELD_HTTP.send(vr, HttpResponse.BodyHandlers.ofString());
if (r.statusCode() == 401) throw new SecurityException("InlineShield HTTP 401 (check INLINESHIELD_API_KEY)");
if (r.statusCode() < 200 || r.statusCode() > 299) throw new IOException("InlineShield HTTP " + r.statusCode());
if (!r.body().trim().startsWith("{")) throw new IOException("InlineShield returned non-JSON");
if (r.body().matches("(?s).*\"error\"\\s*:\\s*\"QUOTA_EXCEEDED\".*")) throw new IOException("InlineShield QUOTA_EXCEEDED");
return r.body();
}
static String q(String v) {
return "\"" + (v == null ? "" : v.replace("\\", "\\\\").replace("\"", "\\\"")) + "\"";
}
// In your signup handler: inlineshieldToken is the betshield_token form field, email the
// submitted email, servletRequest the visitor's HttpServletRequest.
final boolean REQUIRE_INLINESHIELD_TOKEN = false; // true: reject signups that arrive without a token (NO_TOKEN). While your account is over its monthly limit, signups arrive without a token and InlineShield answers QUOTA_EXCEEDED, which follows INLINESHIELD_FAIL_CLOSED even in strict mode.
final boolean INLINESHIELD_FAIL_CLOSED = false; // true: reject signups while InlineShield is unreachable
// Switch on action: "allow" → create the account; "hold" → create it pending and store reviewId; "block" → reject the signup.
String action;
String reviewId = null; // only set for "hold"
try {
String json = inlineshieldPost("/api/v1/siteverify", "{\"token\":" + q(inlineshieldToken) + ",\"email\":" + q(email) + "}");
if (json.matches("(?s).*\"error\"\\s*:\\s*\"NO_TOKEN\".*") && !REQUIRE_INLINESHIELD_TOKEN) {
// No token (script blocked, timed out or missing): judge the signup server-side
json = inlineshieldPost("/api/v1/verify", "{\"email\":" + q(email)
+ ",\"ip\":" + q(servletRequest.getRemoteAddr())
+ ",\"userAgent\":" + q(servletRequest.getHeader("User-Agent")) + "}");
if (!json.matches("(?s).*\"decision\"\\s*:.*")) throw new IOException("InlineShield /verify answered without a decision");
} else if (!json.matches("(?s).*\"success\"\\s*:\\s*true.*")) {
json = "{\"action\":\"block\"}";
}
// Fail-open answers have no "action": fall back to the decision.
java.util.regex.Matcher a = java.util.regex.Pattern.compile("\"action\"\\s*:\\s*\"(allow|hold|block)\"").matcher(json);
action = a.find() ? a.group(1) : (json.matches("(?s).*\"decision\"\\s*:\\s*\"BLOCKED\".*") ? "block"
: json.matches("(?s).*\"decision\"\\s*:\\s*\"QUARANTINED\".*") ? "hold" : "allow");
java.util.regex.Matcher r = java.util.regex.Pattern.compile("\"reviewId\"\\s*:\\s*\"(rev_[A-Za-z0-9_-]+)\"").matcher(json);
if (r.find()) reviewId = r.group(1);
} catch (SecurityException e) {
log.error("InlineShield rejected the request", e);
action = "block";
} catch (IOException | InterruptedException e) {
log.warn("InlineShield unavailable", e);
action = INLINESHIELD_FAIL_CLOSED ? "block" : "allow";
}
if (action.equals("block")) {
throw new ResponseStatusException(HttpStatus.FORBIDDEN, "Signup rejected");
}
if (action.equals("hold")) {
// Create the account as pending (no login, no welcome email) and save reviewId with it.
} else {
// "allow": create the account normally.
}Verify webhooks
Check the signature of each delivery before you trust it. See Webhooks.
// Spring MVC, Java 17+. X-InlineShield-Signature: t=<unix seconds>,v1=<hex HMAC-SHA256 of "<t>.<raw body>">
@PostMapping("/inlineshield/webhook")
public ResponseEntity<Void> inlineshieldWebhook(
@RequestBody byte[] raw, // raw bytes, before any JSON parsing
@RequestHeader(value = "X-InlineShield-Signature", defaultValue = "") String header) throws Exception {
java.util.regex.Matcher m = java.util.regex.Pattern.compile("t=(\\d{1,12}),v1=([0-9a-f]{64})").matcher(header);
if (!m.matches() || Math.abs(Instant.now().getEpochSecond() - Long.parseLong(m.group(1))) > 300) {
return ResponseEntity.badRequest().build(); // malformed header or older/newer than 300 s
}
String secret = System.getenv("INLINESHIELD_WEBHOOK_SECRET");
if (secret == null || secret.isEmpty()) return ResponseEntity.status(500).build(); // never verify with an empty key
Mac mac = Mac.getInstance("HmacSHA256");
mac.init(new SecretKeySpec(secret.getBytes(StandardCharsets.UTF_8), "HmacSHA256"));
mac.update((m.group(1) + ".").getBytes(StandardCharsets.UTF_8));
byte[] expected = mac.doFinal(raw);
if (!MessageDigest.isEqual(expected, HexFormat.of().parseHex(m.group(2)))) { // constant-time
return ResponseEntity.badRequest().build();
}
JsonNode event = new ObjectMapper().readTree(raw); // Jackson, or your JSON library
JsonNode d = event.get("data");
if ("signup.approved".equals(event.path("event").asText())) {
// Activate the account that has reviewId d.get("reviewId") (status "approved").
} else if ("signup.rejected".equals(event.path("event").asText())) {
// Delete or disable it (see d.get("decisionReason")).
}
// Deliveries can repeat: make the handler idempotent (key on event.get("id")).
return ResponseEntity.ok().build();
}Review status
The fallback to webhooks for held sign-ups. See Hold and review.
// Java 11+. Response: { reviewId, status: "pending" | "approved" | "rejected", decisionReason, decidedAt, expiresAt }. Prefer the webhook; poll only as a fallback (limit 60 requests/min per key). 404 = unknown review.
HttpRequest req = HttpRequest.newBuilder(URI.create("https://api.inlineshield.io/api/v1/reviews/" + URLEncoder.encode(reviewId, StandardCharsets.UTF_8)))
.timeout(Duration.ofSeconds(3))
.header("X-Api-Key", System.getenv("INLINESHIELD_API_KEY"))
.GET().build();
HttpResponse<String> resp = HttpClient.newHttpClient().send(req, HttpResponse.BodyHandlers.ofString());
if (resp.statusCode() == 200) {
String json = resp.body(); // parse "status": pending | approved | rejected
}Outcome feedback
Report fraud or legit outcomes by requestId. See Outcome feedback.
// Java 11+. Tell InlineShield how a signup really turned out (fraud or legit), for example after a chargeback, a failed or passed KYC, or bonus abuse, so detection learns from your outcomes. Identify the signup by the requestId InlineShield returned (or send token: the /check token instead). Optional: category (bonus_abuse, multi_account, chargeback, kyc_failed, bot, good_customer, other) and note (up to 200 characters, never shown outside your account). Response 200 { success, outcome, category, changed }; sending the same feedback again changes nothing. 404 = unknown requestId, 422 = the signup is older than 180 days. Not counted toward your monthly limit; limited per key like /verify.
String json = "{\"requestId\":\"" + requestId + "\",\"outcome\":\"fraud\",\"category\":\"chargeback\"}";
HttpRequest req = HttpRequest.newBuilder(URI.create("https://api.inlineshield.io/api/v1/feedback"))
.timeout(Duration.ofSeconds(3))
.header("Content-Type", "application/json")
.header("X-Api-Key", System.getenv("INLINESHIELD_API_KEY"))
.POST(HttpRequest.BodyPublishers.ofString(json)).build();
HttpResponse<String> resp = HttpClient.newHttpClient().send(req, HttpResponse.BodyHandlers.ofString());
if (resp.statusCode() != 200) {
System.err.println("InlineShield feedback not recorded: " + resp.statusCode() + " " + resp.body());
}Errors
The snippets treat a 401 as a configuration error (reject) and every other failure, including 402 QUOTA_EXCEEDED, 429, 5xx and timeouts, as "InlineShield unavailable" (accept and log, unless you choose fail-closed). Every code is on Errors and HTTP codes.