Developer
Webhooks
Receive signed conversion.completed and conversion.failed events instead of polling, and verify each delivery.
Manage endpoints
https://api.markitdown.ai/v1/webhooksList endpointshttps://api.markitdown.ai/v1/webhooksRegister an endpointhttps://api.markitdown.ai/v1/webhooks/{id}Remove an endpointurlrequired | string | Public https URL. Private and reserved addresses are rejected. |
eventTypesrequired | string[] | conversion.completed, conversion.failed, or both. |
The create response includes the signing secret once. Store it; it is not shown again. Managing webhook endpoints does not require an active subscription — only submitting, estimating, and retrying a conversion do.
Events and payload
conversion.completed— a conversion reachedcompletedorcompleted_with_errors.conversion.failed— every source file in a conversion failed.Each fires once per terminal entry; retrying a failed file moves the conversion back to
running, and it fires again the next time the conversion terminates.
{
"id": "cnv_3f7c…:2026-01-01T00:00:04Z",
"type": "conversion.completed",
"data": { "id": "cnv_3f7c…", "object": "conversion", "name": "pdf", "status": "completed", "summary": { "sources": 1, "queued": 0, "running": 0, "completed": 1, "failed": 0 }, "usage": { "credits_charged": 12 }, "links": { "self": "/v1/conversions/cnv_3f7c…", "files": "…/files", "events": "…/events" } }
}data is the conversion summary envelope — the same shape GET /v1/conversions/{id} returns without its files list, so payload size never depends on how many source files the conversion had. Fetch the outputs with GET /v1/conversions/{id}/files after a delivery.
What does a delivery request look like?
Each delivery is an HTTPS POST with a JSON body. Besides the signature headers it carries x-markitdown-event with the event type and x-markitdown-delivery with a delivery id, so you can route and log events before parsing the body. Register one endpoint per destination and subscribe it only to the events it handles.
How do I verify a delivery?
Deliveries follow Standard Webhooks. Each request carries webhook-id, webhook-timestamp, and webhook-signature. Compute HMAC-SHA256 over {webhook-id}.{webhook-timestamp}.{raw body} with your secret (base64-decode it first when it starts with whsec_) and compare the base64 digest with the v1, value in webhook-signature. Reject old timestamps to stop replays. The same delivery also carries x-markitdown-signature: sha256=<hex HMAC of the raw body> for simpler verifiers.
What if my endpoint is down?
Answer with any 2xx status to acknowledge a delivery. Anything else, including a redirect or a timeout, is retried, for up to five attempts in total, with exponential backoff starting at 10 seconds. Handle events idempotently by webhook-id, because a delivery can arrive more than once. If every attempt fails the conversion itself is unaffected: read it with GET /v1/conversions/{id}.
Sources and specs: RFC 9110: HTTP Semantics · RFC 7763: The text/markdown Media Type