This API allows you to 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 simulation campaign reports in your Whalemate analytics
Log in to your Whalemate account
Go to Settings → Integrations → API Keys
Click Generate new API Key
Copy the key (it is only shown once)
Include the API Key in every request via the header:
X-API-KEY: tu_api_key_de_60_caracteres_aqui⚠️ Important:
Do not share your API Key publicly
Rotate the key periodically from the UI
If the key is compromised, revoke it immediately
Create phishing report
POST /api/external/reported-emailsHeader | Value | Required |
|---|---|---|
| Your API Key | ✅ Yes |
|
| ✅ Yes |
|
| ✅ Yes |
Field | Type | Description |
|---|---|---|
| string (email) | Email of the user reporting the phishing |
| string (email) | Email of the suspicious sender |
| string (max 500) | Subject of the reported email |
| string (ISO 8601) | Date the suspicious email was sent |
Field | Type | Description |
|---|---|---|
| string | Sender's display name. If not provided, |
| string | First ~200 characters of the message |
| string | Full message in RFC822 or HTML format |
| array | Email headers (see structure below) |
| array | URLs found in the email |
| array | Attachment metadata (see structure below) |
| integer | Simulation campaign ID. If included, logs the |
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 will receive a 429 error. Wait the time indicated in retry_after (seconds).
curl -X POST https://api.whalemate.io/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('https://api.whalemate.io/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('Report sent:', 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 = 'https://api.whalemate.io/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"Report created: ID {data['data']['id']}")
return data['data']['id']
else:
print(f"Error: {response.json()}")
return None
# Usage
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 = 'https://api.whalemate.io/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 "Report created: 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 does not exist in Whalemate?
The endpoint will return a 422 error. The user must be previously registered in your organization within Whalemate.
Can I send the full content of attachments?
Currently only metadata is accepted (name, type, size). Full content cannot be sent for security and payload size reasons.
How do I know if my report was processed correctly?
The 201 response with the report id confirms it was processed. Additionally, you can:
Check in the Whalemate UI (Email SIEM section)
Your organization's analysts will receive an email notification
Are external reports processed the same way as native add-in reports?
Yes, reports from the API follow 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 in case of compromise
Separate metrics by origin
Does the API Key expire?
No, API Keys do not 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 format (RFC 3339). 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 payload size limit?
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 SIEM incident is created. If you do not send a campaign_id, the behaviour is the same as usual: a ReportedEmail incident is created for analysis.
Never expose the API Key in client-side 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 activity
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) {
// Validation error, do not retry
throw error;
}
if (error.status === 429) {
// Rate limit, wait and retry
const retryAfter = error.headers.get('Retry-After') || 60;
await sleep(retryAfter * 1000);
continue;
}
if (attempt === maxRetries) {
throw error;
}
// Exponential backoff for other errors
await sleep(Math.pow(2, attempt) * 1000);
}
}
}We have an official Postman collection to facilitate testing and integration with the External Phishing Reporting API.
Due to file distribution limitations, the collection is provided upon request.
Please contact the Whalemate support team to obtain the most up-to-date version.