This API lets you integrate Whalemate's phishing reporting into your own systems, such as:
Custom buttons in email clients
Integrations with corporate SIEM
Internal security tools
Automated workflows
Logging of simulation campaign reports in your Whalemate analytics
All endpoints of this API live under the host {{base_url}}. In the examples in this document, replace {{base_url}} with the host Whalemate gives you.
To get the production host (
base_url), contact your Whalemate representative or write to us through the support chat.
Log in to your Whalemate account
Go to Settings → Integrations → API Keys
Click on Generate new API Key
Copy the key (it's only shown once)
Include the API Key in every request via the header:
X-API-KEY: your_60_character_api_key_here⚠️ Important:
Don't share your API Key publicly
Rotate the key periodically from the UI
If the key is compromised, revoke it immediately
Create phishing report
POST {{base_url}}/api/external/reported-emails| Header | Value | Required |
|---|---|---|
X-API-KEY |
Your API Key | ✅ Yes |
Content-Type |
application/json |
✅ Yes |
Accept |
application/json |
✅ Yes |
| Field | Type | Description |
|---|---|---|
reported_by |
string (email) | Email of the user reporting the phishing |
email_from |
string (email) | Email of the suspicious sender |
subject |
string (max 500) | Subject of the reported email |
send_date |
string (ISO 8601) | Send date of the suspicious email |
| Field | Type | Description |
|---|---|---|
from |
string | Sender's name. If not sent, email_from is used |
message_preview |
string | First ~200 characters of the message |
raw_message |
string | Full message in RFC822 or HTML format |
headers |
array | Email headers (see structure below) |
links |
array | URLs found in the email |
attachments |
array | Attachment metadata (see structure below) |
campaign_id |
integer | ID of the simulation campaign. If included, logs the reported event in that campaign instead of creating an incident in the SIEM. (Request a campaign from the support team) |
Headers structure
[{
"name": "Return-Path",
"value": "<[email protected]>"},{
"name": "Received",
"value": "from mail.example.com by mx.google.com..."}]Attachments structure
[{
"name": "invoice.pdf",
"contentType": "application/pdf",
"size": 12345}]Note: The attachment content is not uploaded, only metadata.
{"reported_by": "[email protected]","email_from": "[email protected]","from": "Banco Nacional","subject": "Urgente: Confirma tu cuenta","send_date": "2026-01-19T10:30:00Z","message_preview": "Estimado cliente, detectamos actividad sospechosa...","raw_message": "Content-Type: text/html; charset=UTF-8\n\n<html>...","headers": [
{
"name": "Return-Path",
"value": "<[email protected]>"
},
{
"name": "X-Mailer",
"value": "PhishMailer 2.0"
}],"links": [
"https://suspicious-domain.com/confirm-account?token=abc123",
"https://evil-tracker.com/pixel.gif"],"attachments": [
{
"name": "documento_importante.pdf",
"contentType": "application/pdf",
"size": 45678
}]}{"success": true,"data": {
"id": 12345,
"reported_at": "2026-01-19T14:30:00Z",
"status": "received",
"category": "unknown",
"source": "external_api"},"message": "Phishing report successfully registered"}{"error": "API Key required"}{"error": "Invalid API Key"}{"success": false,"message": "The reporting user does not exist or does not belong to this company","errors": {
"reported_by": [
"The reporting user does not exist or does not belong to this company"
]}}{"message": "The given data was invalid.","errors": {
"email_from": ["The email from field is required."],
"subject": ["The subject field is required."],
"send_date": ["The send date field is required."]}}{"success": false,"message": "Internal error processing the report"}{"message": "Too Many Attempts.","retry_after": 60}Limit: 100 requests per minute per API Key
Reset: Every minute
Response header: X-RateLimit-Remaining indicates remaining requests
If you exceed the limit, you'll get a 429 error. Wait the time given in retry_after (seconds).
In all examples, replace
{{base_url}}with the host Whalemate gives you (see Base URL).
curl -X POST {{base_url}}/api/external/reported-emails \
-H "X-API-KEY: tu_api_key_aqui" \
-H "Content-Type: application/json" \
-H "Accept: application/json" \
-d '{
"reported_by": "[email protected]",
"email_from": "[email protected]",
"subject": "Has ganado un premio",
"send_date": "2026-01-19T10:30:00Z",
"links": ["https://phishing-site.com/click"]
}'const reportPhishing = async (emailData) => {
const response = await fetch('{{base_url}}/api/external/reported-emails', {
method: 'POST',
headers: {
'X-API-KEY': 'tu_api_key_aqui',
'Content-Type': 'application/json',
'Accept': 'application/json'
},
body: JSON.stringify({
reported_by: emailData.userEmail,
email_from: emailData.senderEmail,
subject: emailData.subject,
send_date: emailData.date,
raw_message: emailData.fullMessage,
links: emailData.extractedLinks
})
});
const result = await response.json();
if (response.ok) {
console.log('Reporte enviado:', result.data.id);
} else {
console.error('Error:', result.message);
}
};import requests
from datetime import datetime
def report_phishing(user_email, sender_email, subject, send_date, **kwargs):
url = '{{base_url}}/api/external/reported-emails'
headers = {
'X-API-KEY': 'tu_api_key_aqui',
'Content-Type': 'application/json',
'Accept': 'application/json'
}
payload = {
'reported_by': user_email,
'email_from': sender_email,
'subject': subject,
'send_date': send_date.isoformat(),
**kwargs # message_preview, links, etc.
}
response = requests.post(url, json=payload, headers=headers)
if response.status_code == 201:
data = response.json()
print(f"Reporte creado: ID {data['data']['id']}")
return data['data']['id']
else:
print(f"Error: {response.json()}")
return None
# Uso
report_phishing(
user_email='[email protected]',
sender_email='[email protected]',
subject='Email sospechoso',
send_date=datetime.now(),
links=['https://phishing.com/click']
)<?php
function reportPhishing($apiKey, $reportedBy, $emailFrom, $subject, $sendDate, $options = []) {
$url = '{{base_url}}/api/external/reported-emails';
$data = array_merge([
'reported_by' => $reportedBy,
'email_from' => $emailFrom,
'subject' => $subject,
'send_date' => $sendDate,
], $options);
$ch = curl_init($url);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($data));
curl_setopt($ch, CURLOPT_HTTPHEADER, [
'X-API-KEY: ' . $apiKey,
'Content-Type: application/json',
'Accept: application/json'
]);
$response = curl_exec($ch);
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
$result = json_decode($response, true);
if ($httpCode === 201) {
echo "Reporte creado: ID " . $result['data']['id'] . "\n";
return $result['data']['id'];
} else {
echo "Error: " . $result['message'] . "\n";
return null;
}
}
// UsoreportPhishing(
'tu_api_key_aqui',
'[email protected]',
'[email protected]',
'Email sospechoso',
date('c'),
['links' => ['https://phishing.com/click']]
);What happens if the reporting user doesn't exist in Whalemate?
The endpoint will return error 422. The user must already be registered in your organization within Whalemate.
Can I send the full content of attachments?
Currently only metadata is accepted (name, type, size). The full content can't be sent for security and payload size reasons.
How do I know if my report was processed correctly?
The 201 response with the report's id confirms it was processed. Additionally, you can:
Check in the Whalemate UI (Email SIEM section)
Your organization's analysts will receive a notification by email
Are external reports processed the same as those from native add-ins?
Yes, reports from the API have the same flow:
Initial status: received
Initial category: unknown
Notifications to configured emails
Analysts can classify them normally
They appear in analytics
The only difference is the source field that marks the origin (external_api).
Can I use the same API Key for multiple systems?
Yes, but we recommend generating a different key per system for:
Better traceability
Granular revocation if there's a compromise
Separate metrics by origin
Does the API Key expire?
No, API Keys don't expire automatically. However, we recommend rotating them every 90 days as a security best practice.
What date format should I use for send_date?
Use ISO 8601 (RFC 3339) format. Valid examples:
2026-01-19T10:30:00Z (UTC)
2026-01-19T10:30:00-05:00 (with timezone)
2026-01-19T10:30:00.123Z (with milliseconds)
Is there a limit on payload size?
The request size limit is 2MB. For very large emails, consider sending only the message_preview instead of the full raw_message.
What happens if I include campaign_id in the report?
If you send a valid campaign_id, Whalemate logs the reported event directly in that campaign and it appears in its analytics. No incident is created in the SIEM. If you don't send campaign_id, the behavior is the same as always: a ReportedEmail incident is created for analysis.
Never expose the API Key in client code (JavaScript in the browser)
Always use HTTPS - the API only accepts secure connections
Store the API Key securely (environment variables, secret managers)
Implement retry with exponential backoff for transient errors
Monitor API Key usage to detect anomalous usage
Implement client-side rate limiting to avoid exceeding the limits
Use batch processing if you need to send multiple reports
Cache user validation to avoid repeated lookups
Process reports asynchronously in your system
async function reportWithRetry(emailData, maxRetries = 3) {
for (let attempt = 1; attempt <= maxRetries; attempt++) {
try {
const response = await reportPhishing(emailData);
return response;
} catch (error) {
if (error.status === 422) {
// Error de validación, no reintentar
throw error;
}
if (error.status === 429) {
// Rate limit, esperar y reintentar
const retryAfter = error.headers.get('Retry-After') || 60;
await sleep(retryAfter * 1000);
continue;
}
if (attempt === maxRetries) {
throw error;
}
// Backoff exponencial para otros errores
await sleep(Math.pow(2, attempt) * 1000);
}
}
}We have an official Postman collection to make testing and integration with the External Phishing Reporting API easier.
Due to limitations in file distribution, the collection is provided upon request.
Please contact the Whalemate support team to get the most up to date version.