Flashpoint.AIFlashpoint.AIdocs

Webhooks

Flashpoint.AI receives webhooks from panel providers (Prolific, Dynata) to update fielding status in real time, and from Stripe for billing events. When a respondent completes a study, a panel reaches its target, or a subscription state changes, the provider notifies Flashpoint.AI and the platform updates its records automatically.

These endpoints are inbound only — Flashpoint.AI is the receiver, not the sender. You do not subscribe to them; they are documented here for transparency about how the platform reacts to upstream events.

Prolific webhooks

POST /api/v1/webhooks/prolific

Prolific sends webhooks for respondent-level and study-level events.

Events

EventTriggerWhat Flashpoint.AI does
submission.completedRespondent finished the studyLogs completion, links response to the panel integration
submission.returnedRespondent returned their slotLogs the return so the slot can be re-filled
study.completedStudy reached target completionsSets the panel integration status to completed, emits a PANEL_COMPLETED event

Signature verification

Prolific signs every webhook with HMAC-SHA256. The signature is sent in the X-Prolific-Signature header.

Flashpoint.AI verifies the signature by computing the HMAC of the raw request body using the shared secret and comparing it to the header value. Requests with missing or invalid signatures are rejected with 401.

Payload shape

{
  "event_type": "submission.completed",
  "data": {
    "participant_id": "60a1b2c3d4e5f6...",
    "study_id": "61a2b3c4d5e6f7..."
  }
}

Dynata webhooks

POST /api/v1/webhooks/dynata

Dynata sends webhooks for pricing changes, line-item state transitions, and project-level state changes.

Events

EventTriggerWhat Flashpoint.AI does
REPRICINGDynata raised the per-complete cost mid-fieldingAuto-pauses the integration to protect the budget. Appends the repricing event to the audit log. Emits PANEL_PAUSED.
LINE_ITEM_STATE_CHANGEA line item transitioned state (e.g. LAUNCHED, PAUSED)Records the new state in provider_config for audit trail. No status change on the Flashpoint.AI side.
PROJECT_STATE_CHANGEProject transitioned state (e.g. COMPLETED, CLOSED)Records the new state. If terminal (COMPLETED or CLOSED), marks the integration as completed and emits PANEL_COMPLETED.

Repricing auto-pause

Repricing is a financial safety event. When Dynata raises the cost-per-complete beyond what the customer originally approved, Flashpoint.AI automatically pauses the integration via the Dynata API. The customer must explicitly re-approve the new rate before fielding resumes. The old and new prices are recorded in the integration's provider_config.repricing_events array.

Signature verification

Dynata signs webhooks with the same algorithm as Prolific: HMAC-SHA256 over the raw body, sent in the X-Dynata-Signature header. Missing or invalid signatures are rejected with 401.

Payload shape

{
  "type": "REPRICING",
  "eventId": "evt_abc123",
  "projectId": "proj_xyz789",
  "lineItemId": "li_456",
  "oldPrice": { "cpi": 4.50 },
  "newPrice": { "cpi": 6.25 },
  "reason": "Feasibility adjustment"
}

Stripe webhooks (billing)

POST /webhooks/stripe

Stripe drives subscription state and per-action payments. Every billing-related event is delivered here; the handler is idempotent (events are deduplicated by event.id server-side) and resilient to retries.

Events

EventTriggerWhat Flashpoint.AI does
customer.subscription.createdNew seat-tier subscriptionMirrors purchased seats + Stripe IDs into the team's plan
customer.subscription.updatedSeat count, plan, or status changeRe-mirrors the subscription state
customer.subscription.deletedSubscription cancelledClears the team's subscription state and revokes any active licenses beyond the free-seat cap (newest-first, so the longest-tenured holders keep access)
checkout.session.completedHosted Checkout session paidFlips the matching payment row to succeeded and publishes a fan-out event so downstream services (panel launches, etc.) react
checkout.session.expiredHosted Checkout session lapsed (24h unpaid)Flips the payment row to expired and rolls back any pending action it was funding
payment_intent.succeededDirect PaymentIntent confirmedMarks the intent paid and dispatches the success callback to the originating service
payment_intent.payment_failedDirect PaymentIntent declinedMarks the intent failed and dispatches the failure callback
charge.refundedRefund settled on a chargeAppends an audit event to the corresponding payment intent

Any other event types Stripe delivers are recorded and acknowledged with 200 but produce no state change.

Signature verification

Stripe signs every webhook. The signature is sent in the stripe-signature header and verified server-side using Stripe's standard constructEvent against the configured webhook secret. Missing or invalid signatures are rejected with 400 and Stripe retries with backoff.

Idempotency

Every received event is recorded in a stripe_events table on first delivery. Re-deliveries (Stripe retries, multi-region duplication) collapse to 200 {"received": true, "deduped": true} without re-running any side effects.

Response shape

StatusBodyMeaning
200{"received": true}Event accepted and dispatched
200{"received": true, "deduped": true}Already processed; no-op
400{"error": "missing stripe-signature header"} or "signature verification failed"Stripe will retry
500{"error": "handler error"}Handler raised; Stripe will retry

Verifying signatures

Both panel providers use the same HMAC-SHA256 algorithm. (Stripe uses its own scheme — see the Stripe section above.) Here is a reference implementation for the panel webhooks:

import hashlib
import hmac

def verify_webhook(body: bytes, signature: str, secret: str) -> bool:
    """Verify an HMAC-SHA256 webhook signature.

    Args:
        body: Raw request body bytes.
        signature: Hex-encoded signature from the header.
        secret: Shared secret configured for the provider.

    Returns:
        True if the signature is valid.
    """
    expected = hmac.new(
        secret.encode("utf-8"),
        body,
        hashlib.sha256,
    ).hexdigest()
    return hmac.compare_digest(expected, signature)

Use hmac.compare_digest (constant-time comparison) to prevent timing attacks.

Response behavior

Both endpoints return 200 {"status": "ok"} on success. Providers retry on non-2xx responses, so returning 200 even for internally unroutable events (e.g. unknown project_id) prevents retry storms. Actual processing failures are captured in the audit log for investigation.