DMARC Examiner Webhooks API

Subscribe to events via outbound HTTPS webhooks — Pro plan and above. ## Events emitted The system currently emits a single event: - **`alert.created`** — fired whenever an Alert is created in the organization (triggered by report processing and DNS change detection). Other event types (e.g. `report.received`, `domain.verified`) are NOT emitted today. If your integration depends on them, treat them as out of scope until officially announced. ## Delivery & signing Each delivery is a `POST` to the configured `url` with the JSON body matching `WebhookEvent` (see the schemas section) and the following headers: | Header | Value | |---|---| | `Content-Type` | `application/json` | | `User-Agent` | `DMARC-Examiner-Webhooks/1.0` | | `X-Webhook-Timestamp` | Unix epoch seconds when the request was signed | | `X-Webhook-Signature` | `sha256=` — see signing below | ### How the signature is computed ``` signed_value = "{X-Webhook-Timestamp}.{raw_request_body}" signature = hex(HMAC-SHA256(webhook.secret, signed_value)) header = "sha256=" + signature ``` The signature covers BOTH the timestamp and the exact body bytes. The shared `secret` (returned only at create time and re-fetched via `GET /webhooks`) **never travels on the wire** — only the derived signature does. ### How to verify on your side 1. Reject requests where `|now - X-Webhook-Timestamp| > 5 minutes` (rejects replays). 2. Recompute the expected signature with your stored `secret` and the raw body you received. 3. Compare with the value after `sha256=` using constant-time string comparison (`crypto.timingSafeEqual` in Node, `hmac.compare_digest` in Python, `hash_equals` in PHP). **Do NOT use `==`.** 4. Only after the signature matches, trust the body and parse it as `WebhookEvent`. ## Retries Failed deliveries (network error or non-2xx response) are retried twice with delays of 30 s and 120 s. After 10 consecutive failures the webhook is auto-disabled (`is_active` set to `false`) and no further deliveries are attempted until you re-enable it via `PUT /webhooks/{id}`. Successful deliveries record the timestamp in `last_triggered_at`, the HTTP status in `last_status`, and reset `failure_count` to `0`. The `POST /webhooks/{id}/test` endpoint does NOT touch any of those fields.

Operations 5

GET /api/v1/webhooks List webhook endpoints #
POST /api/v1/webhooks Create a webhook endpoint #
PUT /api/v1/webhooks/{id} Update a webhook #
DELETE /api/v1/webhooks/{id} Delete a webhook #
POST /api/v1/webhooks/{id}/test Send a test event to a webhook #

Work with this as data

Every API here is available over the APIs.io API and to AI agents over MCP.

MCP server

One button, every client — Claude, Cursor, VS Code and the rest.

https://apis.io/mcp

Tools for apis

7 MCP tools reach this
  • find_apisBrowse and filter every API in the catalog.
  • get_api_artifactsOne API's artifacts, grouped by type.
  • get_openapiThe primary OpenAPI for this API.
  • find_similar_apisAPIs that look like this one.
  • apis_io_searchSTART HERE — APIs, providers and tags for one query, each with its total.
  • resolveTurn a domain, URL or GitHub org into the provider it belongs to.
  • find_cohortsEvery scored population of providers in the catalog.
All 92 tools →

Call it yourself

curl for this page
This API
curl "https://apis.io/api/v1/apis/dmarc-examiner-com:dmarc-examiner-com-webhooks-api"
All apis
curl "https://apis.io/api/v1/apis?limit=25"

Calls without X-API-Key need no key. Calls with it need a paid plan — get a key.

Get an API key

Free tier, no form to fill in. Signing in shares your email address with us — we store it to create your key and to recognise you if you sign in with another provider. See our Privacy Policy and Terms.

A second provider on the same verified email joins the account you already have.

OpenAPI Specification

dmarc-examiner-com-webhooks-api-openapi.yml Raw ↑
openapi: 3.2.0
info:
  title: Dmarc Examiner Com Webhooks API
  version: 1.0.0
  contact:
    email: support@dmarc-examiner.com
    url: https://dmarc-examiner.com
  license:
    name: Proprietary
  description: 'Operations tagged Webhooks across 2 of this provider''s published API definitions: dmarc-examiner-com-openapi.yaml, openapi.yaml. Each path carries the servers of the definition it was published in.'
servers:
- url: https://api.dmarc-examiner.com
  description: Production
security:
- ApiKeyAuth: []
tags:
- name: Webhooks
  description: 'Subscribe to events via outbound HTTPS webhooks — Pro plan and above.


    ## Events emitted


    The system currently emits a single event:


    - **`alert.created`** — fired whenever an Alert is created in the

    organization (triggered by report processing and DNS change detection).


    Other event types (e.g. `report.received`, `domain.verified`) are NOT

    emitted today. If your integration depends on them, treat them as out of

    scope until officially announced.


    ## Delivery & signing


    Each delivery is a `POST` to the configured `url` with the JSON body

    matching `WebhookEvent` (see the schemas section) and the following

    headers:


    | Header | Value |

    |---|---|

    | `Content-Type` | `application/json` |

    | `User-Agent` | `DMARC-Examiner-Webhooks/1.0` |

    | `X-Webhook-Timestamp` | Unix epoch seconds when the request was signed |

    | `X-Webhook-Signature` | `sha256=` — see signing below |


    ### How the signature is computed


    ```

    signed_value = "{X-Webhook-Timestamp}.{raw_request_body}"

    signature = hex(HMAC-SHA256(webhook.secret, signed_value))

    header = "sha256=" + signature

    ```


    The signature covers BOTH the timestamp and the exact body bytes. The

    shared `secret` (returned only at create time and re-fetched via

    `GET /webhooks`) **never travels on the wire** — only the derived

    signature does.


    ### How to verify on your side


    1. Reject requests where `|now - X-Webhook-Timestamp| > 5 minutes`

    (rejects replays).

    2. Recompute the expected signature with your stored `secret` and the

    raw body you received.

    3. Compare with the value after `sha256=` using constant-time string

    comparison (`crypto.timingSafeEqual` in Node, `hmac.compare_digest`

    in Python, `hash_equals` in PHP). **Do NOT use `==`.**

    4. Only after the signature matches, trust the body and parse it as

    `WebhookEvent`.


    ## Retries


    Failed deliveries (network error or non-2xx response) are retried twice

    with delays of 30 s and 120 s. After 10 consecutive failures the webhook

    is auto-disabled (`is_active` set to `false`) and no further deliveries

    are attempted until you re-enable it via `PUT /webhooks/{id}`.


    Successful deliveries record the timestamp in `last_triggered_at`, the

    HTTP status in `last_status`, and reset `failure_count` to `0`. The

    `POST /webhooks/{id}/test` endpoint does NOT touch any of those fields.'
paths:
  /api/v1/webhooks:
    get:
      operationId: listWebhooks
      summary: List webhook endpoints
      description: Returns `403 Forbidden` if the plan does not include advanced alerting.
      tags:
      - Webhooks
      responses:
        '200':
          description: List of webhooks
          content:
            application/json:
              schema:
                type: object
                required:
                - data
                properties:
                  data:
                    type: array
                    items:
                      $ref: '#/components/schemas/Webhook'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/FeatureNotAvailable'
        '429':
          $ref: '#/components/responses/RateLimited'
    post:
      operationId: createWebhook
      summary: Create a webhook endpoint
      description: 'URL must be HTTPS. Returns `422 Unprocessable Entity` if the organization

        has reached the maximum number of active webhooks.'
      tags:
      - Webhooks
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/CreateWebhookRequest'
      responses:
        '201':
          description: Webhook created
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Webhook'
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/FeatureNotAvailable'
        '422':
          description: Maximum webhooks reached
          content:
            application/json:
              schema:
                type: object
                required:
                - message
                properties:
                  message:
                    type: string
                    example: Maximum of 5 webhooks per organization
        '429':
          $ref: '#/components/responses/RateLimited'
    servers:
    - url: https://api.dmarc-examiner.com
      description: Production
  /api/v1/webhooks/{id}:
    put:
      operationId: updateWebhook
      summary: Update a webhook
      description: Any combination of `url`, `name`, `is_active` may be updated. URL changes must remain HTTPS.
      tags:
      - Webhooks
      parameters:
      - name: id
        in: path
        required: true
        schema:
          type: string
          format: uuid
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/UpdateWebhookRequest'
      responses:
        '200':
          description: Updated webhook
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Webhook'
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/FeatureNotAvailable'
        '404':
          $ref: '#/components/responses/NotFound'
        '429':
          $ref: '#/components/responses/RateLimited'
    delete:
      operationId: deleteWebhook
      summary: Delete a webhook
      tags:
      - Webhooks
      parameters:
      - name: id
        in: path
        required: true
        schema:
          type: string
          format: uuid
      responses:
        '204':
          description: Webhook deleted
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/FeatureNotAvailable'
        '404':
          $ref: '#/components/responses/NotFound'
        '429':
          $ref: '#/components/responses/RateLimited'
    servers:
    - url: https://api.dmarc-examiner.com
      description: Production
  /api/v1/webhooks/{id}/test:
    post:
      operationId: testWebhook
      summary: Send a test event to a webhook
      description: 'Performs a synchronous delivery of a `webhook.test` event and returns

        the result (status code, response time, error if any).


        Test deliveries are **not** counted as real events: they do NOT update

        `last_triggered_at`, `last_status` or `failure_count` on the webhook.

        Use this endpoint to verify connectivity from the dashboard or CI

        without polluting the production delivery history.'
      tags:
      - Webhooks
      parameters:
      - name: id
        in: path
        required: true
        schema:
          type: string
          format: uuid
      responses:
        '200':
          description: Delivery result
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/WebhookTestResult'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/FeatureNotAvailable'
        '404':
          $ref: '#/components/responses/NotFound'
        '429':
          $ref: '#/components/responses/RateLimited'
    servers:
    - url: https://api.dmarc-examiner.com
      description: Production
components:
  responses:
    NotFound:
      description: Resource not found.
      content:
        application/json:
          schema:
            type: object
            required:
            - message
            properties:
              message:
                type: string
                example: Report not found
    Unauthorized:
      description: 'Authentication failed. Two shapes are possible depending on cause:

        - **Invalid or revoked key** — body is `{"message": "..."}` only.

        - **Missing `X-API-Key` header** — body adds an `error: "unauthorized"` machine-readable code.

        Clients should treat both as the same outcome and read `message` for display.

        '
      content:
        application/json:
          schema:
            type: object
            required:
            - message
            properties:
              error:
                type: string
                description: Present only when the request had no `X-API-Key` header at all.
                enum:
                - unauthorized
              message:
                type: string
                example: Invalid API key
    RateLimited:
      description: Rate limit exceeded.
      content:
        application/json:
          schema:
            type: object
            required:
            - message
            properties:
              message:
                type: string
                example: Rate limit exceeded. Maximum 100 requests per minute.
    FeatureNotAvailable:
      description: 'The organization''s current plan does not include the feature required by this endpoint

        (e.g. `apiAccess`, `forensicReports`, `advancedAlerts`). Distinct from `PlanLimitExceeded`

        (HTTP 402), which signals a *quota* exhausted on a feature the plan does include.


        If a customer downgrades to Free, all `/api/v1/*` requests will start returning this response.

        '
      content:
        application/json:
          schema:
            type: object
            required:
            - error
            - message
            - feature
            - current_plan
            properties:
              error:
                type: string
                enum:
                - feature_not_available
              message:
                type: string
                example: 'Feature "apiAccess" is not available in your current plan: Free. Please upgrade.'
              feature:
                type: string
                example: apiAccess
              current_plan:
                type: string
                example: free
    BadRequest:
      description: "Request payload, body field, or query parameter is missing, malformed,\noutside documented bounds, or fails type/enum validation.\n\nTwo body shapes can occur:\n- **Body validation errors** (e.g. missing `name`, wrong type) come from\n  the controller and have the form `{\"message\": \"...\"}`.\n- **Query parameter errors** (e.g. `limit` out of range, `auth_failure`\n  off-enum, `domain_id` not a UUID) flow through the API exception\n  subscriber and add an `error: \"bad_request\"` machine-readable code:\n  `{\"error\": \"bad_request\", \"message\": \"Query parameter \\\"limit\\\" must be between 1 and 100, got 999\"}`.\n"
      content:
        application/json:
          schema:
            type: object
            required:
            - message
            properties:
              error:
                type: string
                description: Present on query-parameter validation errors. Absent on body validation errors.
                enum:
                - bad_request
              message:
                type: string
                example: Domain name is required
  schemas:
    CreateWebhookRequest:
      type: object
      required:
      - url
      - name
      properties:
        url:
          type: string
          format: uri
          description: Must start with `https://`.
        name:
          type: string
    Webhook:
      type: object
      required:
      - id
      - url
      - name
      - secret
      - is_active
      - failure_count
      - created_at
      - updated_at
      properties:
        id:
          type: string
          format: uuid
        url:
          type: string
          format: uri
          description: HTTPS URL events are delivered to.
          example: https://example.com/dmarc-events
        name:
          type: string
          example: Production
        secret:
          type: string
          description: Shared secret used to sign deliveries (HMAC-SHA256). Verify the `X-Signature` header on each incoming POST.
        is_active:
          type: boolean
        last_triggered_at:
          type:
          - string
          - 'null'
          format: date-time
        last_status:
          type:
          - integer
          - 'null'
          description: HTTP status code of the most recent delivery attempt.
        failure_count:
          type: integer
          description: Consecutive failure count. Resets on a successful delivery.
        created_at:
          type: string
          format: date-time
        updated_at:
          type: string
          format: date-time
    UpdateWebhookRequest:
      type: object
      minProperties: 1
      properties:
        url:
          type: string
          format: uri
          description: Must start with `https://` if supplied.
        name:
          type: string
        is_active:
          type: boolean
      description: 'At least one of `url`, `name` or `is_active` MUST be supplied — an empty

        object returns `400`.

        '
    WebhookTestResult:
      type: object
      required:
      - success
      - status_code
      - response_time_ms
      - error
      properties:
        success:
          type: boolean
          description: True only when the sink answered with a 2xx status. Network errors and non-2xx responses both return `false`.
        status_code:
          type:
          - integer
          - 'null'
          description: HTTP status returned by the sink. `0` when the request never reached it (timeout, DNS failure, connection refused, etc.).
          example: 200
        response_time_ms:
          type: integer
          minimum: 0
          description: Milliseconds elapsed between request start and the response (or the failure point).
          example: 138
        error:
          type:
          - string
          - 'null'
          description: Error message when the request could not be completed. Null on a normal HTTP exchange (even if the status was non-2xx).
  securitySchemes:
    ApiKeyAuth:
      type: apiKey
      in: header
      name: X-API-Key
      description: API key in format `dex_` followed by 32 hex characters. Create keys in Settings > API Keys.
x-refined-from:
- dmarc-examiner-com-openapi.yaml
- openapi.yaml