PowerDMARC and Splunk Integration Guide
PowerDMARC → Solution home → Integrations → SIEM
With PowerDMARC's Splunk integration, you can ingest and monitor your email authentication and domain security data directly within your Splunk environment. By leveraging the PowerDMARC API, organizations can build a streamlined SIEM integration without complex configurations — connect, run, and gain centralized visibility into their email security posture across all domains.
This guide focuses on setup and ingestion. Splunk dashboards and advanced visualizations are out of scope.
API Documentation
Swagger Documentation: https://app.powerdmarc.com/swagger-ui/index.html
Alternative Documentation: https://api.powerdmarc.com/
Note: Naming conventions (index names, sourcetypes, file paths) are suggestions, not requirements. Adjust them to match your environment's standards.
What the Script Collects
The integration script pulls two data sets from the PowerDMARC API:
Aggregate reports are collected for every domain in your account, across the compliant, failed, and forwarded statuses. Domains are enumerated automatically via /api/v1/domains.
Architecture Overview
PowerDMARC API
↓
Python script (scheduled via cron / systemd timer / Task Scheduler)
↓
Splunk HTTP Event Collector (HEC)
↓
Splunk (search, dashboards, alerts, correlation)
Splunk receives data through its HTTP Event Collector (HEC) endpoint, which allows secure data ingestion from external sources.
The script also supports writing newline-delimited JSON files instead of — or in addition to — HEC, for environments where outbound HTTPS to the Splunk HEC port isn't permitted. See Alternative: File Monitor Ingestion.
Prerequisites
Splunk Enterprise or Splunk Cloud with administrative access
Permission to create HEC tokens, create indexes, and configure data inputs
Python 3.7 or later on the system running the script
A PowerDMARC API bearer token with permission to access audit logs and aggregate reports
Network connectivity from the script host to:
PowerDMARC API — https://app.powerdmarc.com (TCP 443)
Your Splunk HEC endpoint (TCP 8088 for Splunk Enterprise, TCP 443 for Splunk Cloud)
Splunk Configuration
Step 1: Create a Dedicated Index
Navigate to Settings → Indexes
Click New Index
Configure:
Index Name: powerdmarc
Index Data Type: Events
App: search (or your preferred app)
Leave other settings at their defaults, or adjust for your retention requirements
Click Save
Step 2: Enable HTTP Event Collector (HEC)
Navigate to Settings → Data inputs
Click HTTP Event Collector
Click Global Settings
Configure:
All Tokens: Enabled
Enable SSL: Enabled (recommended)
HTTP Port Number: 8088 (default)
Click Save
Splunk Cloud customers: HEC is enabled by default and listens on port 443. You do not need to change global settings, but you may need to file a support request to enable HEC on some stack types.
Step 3: Create the HEC Token
Still in Settings → Data inputs → HTTP Event Collector, click New Token
Configure token settings:
Name: PowerDMARC_Integration
Source name override: powerdmarc:api
Description: Token for PowerDMARC audit log and aggregate report ingestion
Click Next
Input settings:
Source type: Select Automatic
Allowed Indexes: include powerdmarc
Default Index: powerdmarc
Click Review, then Submit
Important: Copy and save the token value immediately — you cannot retrieve it later.
Why "Automatic" matters: the script sets a per-event sourcetype (dmarc:audit or dmarc:aggregate) in the HEC payload. Selecting a fixed sourcetype on the token would override those values and merge both data sets into one sourcetype.
Integration Script Setup
Step 4: Prepare the Python Environment
The script has a single third-party dependency: requests.
Option A — Online installation (recommended)
pip3 install requests
Option B — Offline installation
On a machine with internet access:
pip3 download requests -d ./packages
Transfer the packages folder to the target system, then:
pip3 install --no-index --find-links=./packages requests
Verify the installation:
python3 -c "import requests; print(requests.__version__)"
Any reasonably current version (2.25 or later) is fine.
Step 5: Deploy the Script
Create a dedicated service account and directory structure rather than running the integration as root:
sudo useradd -r -s /usr/sbin/nologin dmarc
sudo mkdir -p /opt/dmarc /etc/dmarc /var/lib/dmarc /var/log/dmarc
sudo chown dmarc:dmarc /var/lib/dmarc /var/log/dmarc
sudo chmod 750 /var/lib/dmarc /var/log/dmarc
Copy dmarc_to_splunk.py into place:
sudo install -o dmarc -g dmarc -m 750 dmarc_to_splunk.py /opt/dmarc/
Step 6: Configure the Script
Every setting can be supplied either by editing the config dictionary in main() or by setting an environment variable. Environment variables are strongly recommended so that credentials never live inside the script file.
HEC endpoint URL formats:
Splunk Enterprise / on-premises: https://your-splunk-instance:8088/services/collector/event
Splunk Cloud: https://http-inputs-<your-stack>.splunkcloud.com/services/collector/event
Splunk Cloud hostnames vary by stack age and type — some use http-inputs-<stack>.splunkcloud.com on port 443, others use a .splunkcloud.com:8088 form. Confirm yours under Settings → Data inputs → HTTP Event Collector in your Splunk Cloud console rather than assuming.
Create the credentials file:
sudo tee /etc/dmarc/splunk.env >/dev/null <<'EOF'
DMARC_API_KEY=your_powerdmarc_bearer_token
SPLUNK_HEC_URL=https://your-splunk-instance:8088/services/collector/event
SPLUNK_HEC_TOKEN=your_hec_token
SPLUNK_INDEX=powerdmarc
DMARC_DAYS_TO_FETCH=7
EOF
sudo chown root:dmarc /etc/dmarc/splunk.env
sudo chmod 640 /etc/dmarc/splunk.env
Step 7: Test Connectivity
The script accepts a --test flag that sends a single probe event to HEC and exits. This validates the token, URL, TLS chain, and firewall path without waiting for a full collection run:
sudo -u dmarc bash -c 'set -a; . /etc/dmarc/splunk.env; set +a; python3 /opt/dmarc/dmarc_to_splunk.py --test'
Expected output:
============================================================
PowerDMARC to Splunk Integration
Output mode: hec
============================================================
Testing Splunk HEC connectivity...
Sent batch of 1 dmarc:audit events (1/1)
HEC summary for dmarc:audit — sent: 1, failed: 0, total: 1
Confirm the probe arrived:
index=powerdmarc action="integration_connectivity_test"
Step 8: Run a Full Collection
sudo -u dmarc bash -c 'set -a; . /etc/dmarc/splunk.env; set +a; python3 /opt/dmarc/dmarc_to_splunk.py'
Expected output (abbreviated):
============================================================
PowerDMARC to Splunk Integration
Output mode: hec
============================================================
Processing DMARC aggregate reports...
Fetching aggregate reports from 2026-01-30 to 2026-02-06
Fetching all domains...
Fetched page 1: 24 domains (total so far: 24)
Fetched 24 total domains
Progress: 1.4% (1/72) | Domain 1/24: example.com | ETA: 4.7 min remaining
...
Processed 318 unique aggregate report events
Sent batch of 318 dmarc:aggregate events (318/318)
HEC summary for dmarc:aggregate — sent: 318, failed: 0, total: 318
Processing audit logs...
Fetching audit logs from 2026-01-30 to 2026-02-06
Fetched 15 total audit log entries
Processed 15 unique audit log events from last 7 days
HEC summary for dmarc:audit — sent: 15, failed: 0, total: 15
============================================================
Integration completed successfully
============================================================
The first run will be the longest, since it collects the full lookback window. Subsequent runs skip anything already ingested (see Deduplication).
Schedule Automated Execution
Linux/Unix (cron)
sudo crontab -u dmarc -e
Hourly:
0 * * * * set -a; . /etc/dmarc/splunk.env; set +a; /usr/bin/python3 /opt/dmarc/dmarc_to_splunk.py >> /var/log/dmarc/run.log 2>&1
The script logs to standard output, so the redirect above is what captures the run log. Add a logrotate rule for /var/log/dmarc/run.log in production.
Sizing the interval. The script paces itself at roughly 2 seconds between API calls to stay inside PowerDMARC's rate limit, and it makes three aggregate requests per domain. A rough estimate of aggregate collection time is domains × 3 × 4 seconds — about 5 minutes for 25 domains, but nearly 2 hours for 500. If your account has more than ~100 domains, an hourly schedule will overlap itself. Either:
Split the schedule — run audit log collection hourly and aggregate collection once daily, or
Add a lock file (flock) so overlapping runs exit cleanly:
0 * * * * /usr/bin/flock -n /tmp/dmarc-splunk.lock -c 'set -a; . /etc/dmarc/splunk.env; set +a; /usr/bin/python3 /opt/dmarc/dmarc_to_splunk.py' >> /var/log/dmarc/run.log 2>&1
Linux (systemd timer)
A systemd timer is generally preferable to cron for this workload — it handles the environment file natively, prevents overlapping runs, and sends output to the journal.
/etc/systemd/system/dmarc-splunk.service:
[Unit]
Description=PowerDMARC to Splunk ingestion
After=network-online.target
[Service]
Type=oneshot
User=dmarc
Group=dmarc
EnvironmentFile=/etc/dmarc/splunk.env
ExecStart=/usr/bin/python3 /opt/dmarc/dmarc_to_splunk.py
/etc/systemd/system/dmarc-splunk.timer:
[Unit]
Description=Run PowerDMARC to Splunk ingestion hourly
[Timer]
OnCalendar=hourly
Persistent=true
[Install]
WantedBy=timers.target
Enable it:
sudo systemctl daemon-reload
sudo systemctl enable --now dmarc-splunk.timer
sudo systemctl list-timers dmarc-splunk.timer
journalctl -u dmarc-splunk.service -f
Windows (Task Scheduler)
Open Task Scheduler and click Create Task
General tab:
Name: PowerDMARC Splunk Integration
Security options: Run whether user is logged on or not
Triggers tab: New → Begin: On a schedule → Daily, repeat every 1 hour
Actions tab: New → Start a program
Program: python.exe
Arguments: C:\dmarc\dmarc_to_splunk.py
Click OK
On Windows, set the configuration values in the config dictionary directly or define the environment variables at the machine level, and change output_dir / state_file to Windows paths such as C:\dmarc\logs and C:\dmarc\state\state.json.
Validate Data Ingestion in Splunk
Audit logs
index=powerdmarc sourcetype=dmarc:audit
| sort - _time
| head 20
| table _time, user_name, action, ip_address, admin_username
Fields you should see:
user_name — user who performed the action
action — description of the action performed
ip_address — IP address of the user
admin_username — administrator account, where applicable
timestamp — original PowerDMARC event time
Aggregate reports
index=powerdmarc sourcetype=dmarc:aggregate
| stats sum(email_volume) as volume, avg(dmarc_pass_percentage) as avg_pass by domain_name
| sort - volume
Aggregate events carry per-domain, per-sending-source counts and percentages for DMARC, SPF, and DKIM, plus the policy actually applied.
Sample event structures
dmarc:audit
{
"sourcetype": "dmarc:audit",
"timestamp": "2026-02-04 14:29:24",
"user_name": "John Doe",
"action": "Updated attached domains",
"ip_address": "12.111.67.123",
"admin_username": "N/A",
"other_info": "N/A"
}
dmarc:aggregate (abbreviated)
{
"sourcetype": "dmarc:aggregate",
"timestamp": "2026-02-06",
"report_date_from": "2026-01-30",
"report_date_to": "2026-02-06",
"domain_id": 1234,
"domain_name": "example.com",
"sending_source": "Google",
"status": "compliant",
"email_volume": 4821,
"dmarc_pass_count": 4810,
"dmarc_pass_percentage": 99.77,
"spf_align_percentage": 99.77,
"dkim_align_percentage": 100.0
}
Event timestamps
The script sets the HEC time field from each event's own timestamp where it can parse one, so _time reflects when the event occurred rather than when it was ingested. This matters on the first run: without it, a seven-day backfill would land entirely in the current minute and look wrong in every time-series panel.
Deduplication
The script maintains a state file (default /var/lib/dmarc/state.json) containing SHA-256 fingerprints of every event already delivered. On each run, events matching a stored fingerprint are skipped. Fingerprints older than 14 days are pruned automatically to keep the file from growing without bound.
Fingerprints are only committed after delivery succeeds, so a failed HEC POST leaves those events eligible for retry on the next run rather than silently dropping them.
Two operational consequences:
The state file must persist across runs and reboots. Don't place it in /tmp or inside a container layer that gets discarded.
Deleting the state file causes the next run to re-ingest the full lookback window. That's the correct way to force a backfill, but expect duplicates in Splunk if the data is already there.
Alternative: File Monitor Ingestion
If outbound access to the HEC port isn't available, set DMARC_OUTPUT_MODE=file (or both). The script writes newline-delimited JSON to output_dir, one file per run per data set:
/var/log/dmarc/dmarc_aggregate_20260206_140312.json
/var/log/dmarc/audit_logs_20260206_140312.json
Configure a Splunk forwarder to monitor that directory. In $SPLUNK_HOME/etc/system/local/inputs.conf:
[monitor:///var/log/dmarc/dmarc_aggregate_*.json]
disabled = false
index = powerdmarc
sourcetype = dmarc:aggregate
[monitor:///var/log/dmarc/audit_logs_*.json]
disabled = false
index = powerdmarc
sourcetype = dmarc:audit
And in props.conf, so that events are split per line and timestamped correctly:
[dmarc:aggregate]
INDEXED_EXTRACTIONS = json
KV_MODE = none
SHOULD_LINEMERGE = false
TIME_PREFIX = "report_date_to":\s*"
TIME_FORMAT = %Y-%m-%d
[dmarc:audit]
INDEXED_EXTRACTIONS = json
KV_MODE = none
SHOULD_LINEMERGE = false
TIME_PREFIX = "timestamp":\s*"
TIME_FORMAT = %Y-%m-%d %H:%M:%S
The Splunk user needs read access to the directory — add it to the dmarc group, or relax the directory mode to 0755. Add a cleanup job (find /var/log/dmarc -name '*.json' -mtime +7 -delete) so old output files don't accumulate.
Troubleshooting
No data appearing in Splunk
Run with --test first to isolate whether the problem is HEC or the PowerDMARC side
Verify the HEC token is correct and enabled (Settings → Data inputs → HTTP Event Collector)
Confirm the token's allowed indexes include powerdmarc
Check that the index exists and your role has access to search it
Verify firewall rules permit outbound HTTPS from the script host to the HEC endpoint
Review the run log for HEC returned HTTP … lines — Splunk's error body names the specific problem
HTTP 403 "Invalid token" from HEC
The token value is wrong, disabled, or belongs to a different Splunk stack. Note that the header format is Authorization: Splunk <token> — not Bearer.
HTTP 400 "Incorrect index"
The token doesn't permit the index the script is targeting. Either add powerdmarc to the token's allowed index list, or change SPLUNK_INDEX to an index the token already allows.
SSL certificate errors
Install a certificate trusted by the script host — this is the correct fix. As a temporary measure in non-production environments only, set SPLUNK_VERIFY_SSL=false. Never do this in production; it disables the protection that makes HEC over TLS meaningful.
PowerDMARC API authentication failures
Verify the API token is valid and hasn't expired
Confirm the token has permission for both audit logs and aggregate reports
Confirm the API base URL is reachable from the host
Script runs but no logs fetched
Check whether audit logs actually exist for the lookback period
Increase DMARC_DAYS_TO_FETCH temporarily
Remember that deduplication suppresses previously ingested events — a run reporting Processed 0 unique audit log events after a successful earlier run is normal, not a fault
Permission denied on startup
The service account can't create or write /var/lib/dmarc or /var/log/dmarc. Pre-create both directories and chown them to the account the script runs as, as shown in Step 5.
Run takes longer than the schedule interval
See the sizing note under Schedule Automated Execution. Add flock or split the collection schedule.
Next Steps
With data flowing, you can:
Build custom dashboards for DMARC compliance trending by domain and sending source
Alert on audit events such as policy changes or logins from unexpected IP ranges
Alert on compliance regressions — a sending source whose dmarc_pass_percentage drops sharply week over week
Correlate PowerDMARC data with other security logs (mail gateway, identity, EDR)
Build compliance and executive reporting from the aggregate data set
Recommended Enhancements
Additional API endpoints: extend the script to fetch forensic reports or per-domain configuration data
Log rotation: add a logrotate rule for the script's run log in production
Failure notifications: wrap the script in a call that alerts on a non-zero exit status, or alert in Splunk on absence of the expected hourly event volume
Package as a Splunk TA: bundle the inputs, props, and index-time configuration as a Technology Add-on for easier distribution
Secrets management: replace the environment file with a secrets manager (Vault, AWS Secrets Manager, systemd credentials) where one is available
Security Considerations
Store credentials outside the script. Use the environment file (mode 640, owned root:dmarc) or a secrets manager. Never commit tokens to version control.
Keep TLS verification enabled. SPLUNK_VERIFY_SSL defaults to true for a reason.
Run as a dedicated unprivileged account. The integration needs no root privileges.
Restrict the HEC token. Limit it to the powerdmarc index only.
Rotate both tokens on a schedule — the PowerDMARC bearer token and the Splunk HEC token.
Monitor execution. Alert on failed runs and on unexpected gaps in ingestion.
Review Splunk access controls. Audit log data identifies users and source IPs; restrict the index to roles that need it.
Support and Resources
PowerDMARC API documentation: https://api.powerdmarc.com/
Splunk HEC documentation: https://docs.splunk.com/Documentation/Splunk/latest/Data/UsetheHTTPEventCollector
Splunk Answers: https://community.splunk.com/