# interactsh
Async client for polling out-of-band interaction servers. Generate unique URLs, inject them into payloads, and correlate DNS/HTTP interactions back to the original probe. Used for detecting blind XSS, SSRF, SQL injection, and other vulnerabilities where the target makes outbound requests.
```rust
use interactsh::{InteractshClient, ClientConfig, InteractionContext};
#[tokio::main]
async fn main() -> interactsh::Result<()> {
let client = InteractshClient::new(ClientConfig::default()).await?;
// Generate a URL tagged with context
let url = client.generate_url(
InteractionContext::new("blind-xss")
.with_attribute("target", "https://app.example.com")
)?;
println!("Payload URL: {}", url.url);
// Poll for interactions
let interactions = client.poll().await?;
for interaction in interactions {
println!("Got {} interaction from {:?}",
interaction.event.protocol,
interaction.context.label
);
}
Ok(())
}
```
## Why this exists
Blind vulnerabilities require out-of-band detection. You inject a URL into a payload. If the target is vulnerable, it makes a request back to that URL. Most tools either lack OOB detection entirely or embed hardcoded interactsh logic that cannot be reused.
This crate provides an async client that handles URL generation with cryptographic nonces, correlation ID management, and interaction polling. It works with public interactsh servers or self-hosted instances.
## URL generation
Each generated URL contains:
- A correlation ID (configurable length, default 14 chars)
- A unique nonce (configurable length, default 16 chars)
- The server hostname
The combination ensures global uniqueness while letting you correlate interactions back to specific probes.
```rust
let url = client.generate_url(InteractionContext::new("probe-123"))?;
// URL format: {correlation_id}{nonce}.{server}
// Example: abc123def456ghi.oast.pro
```
## Context tracking
Attach metadata to URLs for later correlation:
```rust
let context = InteractionContext::new("ssrf-test")
.with_attribute("template_id", "CVE-2021-44228")
.with_attribute("target_host", "api.internal");
let url = client.generate_url(context)?;
```
When polling returns interactions, the context is restored from the nonce mapping.
## Polling
The `poll()` method retrieves all interactions for your correlation ID:
```rust
let interactions = client.poll().await?;
for item in interactions {
println!("Protocol: {}", item.event.protocol);
println!("Raw request: {:?}", item.event.raw_request);
println!("Matched context: {}", item.context.label);
}
```
Unknown interactions (wrong correlation ID or forgotten nonces) are filtered out automatically.
## Configuration
```rust
use interactsh::ClientConfig;
let config = ClientConfig {
server: "oast.fun".to_string(),
token: Some("api-key".to_string()),
correlation_id_length: 20,
nonce_length: 12,
..ClientConfig::default()
};
```
Load from TOML:
```rust
let config = ClientConfig::from_toml_str(r#"
server = "oast.fun"
token = "secret-api-key"
correlation_id_length = 16
"#)?;
```
## Error handling
Errors are typed by stage:
- `ConfigProblem::Empty` — Missing required fields
- `ConfigProblem::MustBeGreaterThanZero` — Invalid length settings
- `TransportStage::Send` — Network failure
- `TransportStage::Timeout` — Request timeout
- `TransportStage::ReadBody` — Response body read failure
## Self-hosted servers
Point at your own interactsh instance:
```rust
let config = ClientConfig {
server: "oast.internal.corp".to_string(),
default_scheme: "https".to_string(),
..ClientConfig::default()
};
```
## Contributing
Pull requests are welcome. There is no such thing as a perfect crate. If you find a bug, a better API, or just a rough edge, open a PR. We review quickly.
## License
MIT. Copyright 2026 CORUM COLLECTIVE LLC.
[](https://crates.io/crates/interactsh)
[](https://docs.rs/interactsh)