Pulse

Ingest API

Send error and event payloads to Pulse. Use your project API key from project settings.

Endpoint

POST https://pulse.dply.io/api/ingest

Content-Type: application/json

Authentication

Pass your project API key using the first match below (highest priority first):

  1. Header: X-Api-Key: <your_api_key>
  2. Bearer: Authorization: Bearer <your_api_key>
  3. Query (GET only): ?api_key=... on GET /api/projects/{id}/events
  4. Body: api_key in the JSON body (typical for POST /api/ingest)

The key is validated against a single project. Invalid or missing key returns 401.

Versioned routes & read API

The same ingest and read handlers are exposed under /api/v1/... for forward compatibility (for example POST https://pulse.dply.io/api/v1/ingest). Unversioned /api/ingest remains supported.

Team visibility vs API keys: Project API keys gate POST /api/ingest and GET /api/projects/{id}/events. Team-based project visibility applies to the signed-in web app only, not to these key-authenticated endpoints. Organization ingest IP allowlists (Billing) apply to every request authenticated with a project API key (ingest and read-events, including /api/v1/...), not to the browser UI.

Idempotency

Optional header Idempotency-Key (per project). Retries with the same key within 24 hours receive the same 202 response body and do not enqueue another event. Keys are stored in the application database (row expires after 24 hours).

Outbound webhooks (integrations)

In Project → Settings, add an outbound webhook URL. When the first event for a new fingerprint (issue) is stored, Pulse queues an HTTP POST with Content-Type: application/json. Header X-Pulse-Event: issue.created and X-Pulse-Signature: sha256=<hmac> where <hmac> is hex-encoded HMAC-SHA256 of the raw request body (the exact UTF-8 bytes sent as the entity body) using your signing secret (shown once when the webhook is created). Payload includes event, version, project, issue (fingerprint, first event id, message, level, etc.), and urls.event to open the event in the Pulse UI.

Retries & verification

Verify signatures using the same raw body your endpoint receives (before JSON parsing). HTTP 5xx and 429 responses are retried up to 4 attempts with increasing backoff (10s, 60s, 300s) when delivery runs on a queue worker (not the sync driver). Other status codes (including 4xx) are logged and not retried. Recent attempts appear under Project → Settings → Outbound webhooks.

Request body

All fields are optional except: at least one of message or exception_class is required.

Field Type Description
stringError or log message. Required if exception_class is omitted.
stringException/error class name. Required if message is omitted.
stringOne of: error, warning, info, debug.
stringSource file path or URL.
integerLine number.
stringStack trace text (e.g. from error.stack or exception backtrace).
objectArbitrary key-value context (e.g. user id, request id).
stringPage or request URL where the error occurred.
stringDeployment environment (e.g. production, staging). Shown as a tag and available for filtering—similar to Sentry.
stringRelease or app version (e.g. git SHA, semver). Filterable in the UI and API. Each distinct value is also listed on the project Releases page (first/last seen and open issue counts).
stringHost or server identifier where the event was captured.
objectOptional. Affected user: id, email, username, name, ip_address. Merged into context.user for display on the event page.
arrayUp to 50 items; each may include type, category, message, level, timestamp, data (object). Shown as a timeline on the event page.
stringISO 8601 timestamp. Defaults to server time if omitted.
stringOptional. When provided (non-empty), used for grouping; otherwise the server computes a hash from message + exception_class + file + line.
stringOptional. URL to a source map (for future symbolication). Stored but not processed yet. Must be a valid URL if provided.

Source maps (planned)

Future versions may support symbolication: turning minified JavaScript stack traces into readable file:line information using source maps. For now, the following optional fields are accepted and stored but not processed. Integrators can send them so that when symbolication is implemented, existing clients do not need to change.

No symbolication is performed today. These fields can be included in the request body and will be stored where supported (e.g. source_map_url is persisted on the error event).

Responses

Example: cURL

curl -X POST "https://pulse.dply.io/api/ingest" \
  -H "Content-Type: application/json" \
  -H "X-Api-Key: YOUR_PROJECT_API_KEY" \
  -d '{
    "message": "Something went wrong",
    "exception_class": "RuntimeException",
    "level": "error",
    "file": "app/Http/Controllers/ExampleController.php",
    "line": 42,
    "stack_trace": "#0 vendor/...",
    "context": { "request_id": "req-abc" },
    "environment": "production",
    "release": "my-app@2.4.1",
    "server_name": "web-1",
    "user": { "id": "42", "email": "user@example.com" },
    "breadcrumbs": [
      { "type": "navigation", "message": "GET /checkout", "timestamp": "2025-03-17T11:59:50Z" }
    ],
    "url": "https://example.com/page",
    "timestamp": "2025-03-17T12:00:00Z"
  }'

Same request using Bearer instead of X-Api-Key:

curl -X POST "https://pulse.dply.io/api/ingest" \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer YOUR_PROJECT_API_KEY" \
  -d '{"message":"Something went wrong","level":"error"}'

Example: PHP

$response = \Illuminate\Support\Facades\Http::withHeaders([
    'X-Api-Key' => config('pulse.api_key'),
])
->post(config('pulse.ingest_url') . '/api/ingest', [
    'message' => $exception->getMessage(),
    'exception_class' => get_class($exception),
    'level' => 'error',
    'file' => $exception->getFile(),
    'line' => $exception->getLine(),
    'stack_trace' => $exception->getTraceAsString(),
    'url' => request()->fullUrl(),
    'timestamp' => now()->toIso8601String(),
]);

Example: JavaScript (fetch)

await fetch('https://pulse.dply.io/api/ingest', {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json',
    'X-Api-Key': 'YOUR_PROJECT_API_KEY',
  },
  body: JSON.stringify({
    message: error.message,
    exception_class: error.name,
    level: 'error',
    file: error.fileName || window.location.href,
    line: error.lineNumber,
    stack_trace: error.stack,
    url: window.location.href,
    timestamp: new Date().toISOString(),
  }),
});

Get your project API key from Project → Settings in the dashboard. Rate limit: 60 requests per minute per API key (or per IP if key is not resolved).