> ## Documentation Index
> Fetch the complete documentation index at: https://docs.melso.ai/llms.txt
> Use this file to discover all available pages before exploring further.

> ## Agent Instructions
> Melso MCP: https://mcp.melso.ai/mcp (OAuth; one company per connection). Documentation-search MCP: https://docs.melso.ai/mcp; it does not act on customer accounts.
>
> Use the live tool list and respect company membership. Ask the person to approve the exact invoice or tax return version in the signed-in app. Melso's professionals prepare and file included returns after approval; an AI assistant does not file. A prepared file or an approval is not evidence of filing: only the AEAT's reference and receipt are. Keep credentials out of chat and logs.

# Webhooks

> Receive signed events when a return is calculated, an invoice is registered, a mandate is active or the administration writes to a company.

<Info>
  Preview. The signing and retry scheme below is the one Melso's outbound
  webhooks use in production today; the event types are new.
</Info>

Register an HTTPS endpoint with
[Create a webhook endpoint](/api/reference/webhook-endpoints/create-webhook-endpoint)
and choose its event types. An endpoint receives the events of every company on
your partner account.

## The request

Melso sends a `POST` with a JSON body:

```json theme={null}
{
  "id": "8d0c6f9e-2b1a-4c3d-9e8f-7a6b5c4d3e2f",
  "type": "tax_return.calculated",
  "created_at": "2026-10-02T06:00:04Z",
  "company_id": "1b2c3d4e-5f6a-4b7c-8d9e-0f1a2b3c4d5e",
  "data": {
    "id": "5e4d3c2b-1a0f-4e9d-8c7b-6a5f4e3d2c1b",
    "model": "303",
    "period": "2026-Q3",
    "status": "draft",
    "amount": "2210.40",
    "version": 1
  }
}
```

`data` is the object as its `GET` returns it. Two headers come with it:

| Header | Value |
| - | - |
| `Melso-Event-ID` | The event's `id`. A retry carries the same one, so use it to ignore duplicates. |
| `Melso-Signature` | `t=<unix seconds>,v1=<hex HMAC-SHA256>` |

## Verify the signature

The signature is an HMAC-SHA256 of the timestamp, a dot and the raw body, keyed
with your signing secret (the whole `whsec_…` string). Verify it on the raw
bytes, before parsing JSON, and reject a timestamp older than five minutes.

```python theme={null}
import hashlib
import hmac
import time


def verify(secret: str, header: str, body: bytes, tolerance: int = 300) -> bool:
    parts = dict(p.split("=", 1) for p in header.split(","))
    timestamp, signature = parts["t"], parts["v1"]
    if abs(time.time() - int(timestamp)) > tolerance:
        return False
    expected = hmac.new(secret.encode(), timestamp.encode() + b"." + body, hashlib.sha256).hexdigest()
    return hmac.compare_digest(expected, signature)
```

Each attempt is signed again with its own timestamp.

## Respond and retries

Answer any `2xx` within 15 seconds; do the work afterwards. Redirects are not
followed. A failed delivery is retried after 30 seconds, then with a doubling
wait up to one hour, for 24 hours. After that it is `failed`; you can
[resend it](/api/reference/webhook-endpoints/resend-webhook-delivery)
and see every attempt in
[List webhook deliveries](/api/reference/webhook-endpoints/list-webhook-deliveries).

Events can arrive out of order. When the order matters, read the object again
with its `GET`.

## Event types

| Event | When |
| - | - |
| `company.onboarding_completed` | The owner finished the hosted onboarding. |
| `mandate.signed` | The signer signed a mandate. |
| `mandate.activated` | The administration registered it; Melso can act. |
| `mandate.rejected` | The administration refused it. |
| `mandate.revoked` | The company revoked it. |
| `invoice.issued` | A person issued an invoice: it has its series and number. |
| `invoice.registered` | VERI\*FACTU or TicketBAI registered it; the PDF is ready. |
| `invoice.registration_failed` | The registration failed. Retry the issue call. |
| `invoice.sent` | The invoice was emailed to the customer. |
| `invoice.paid` | The invoice was marked paid. |
| `document.processed` | A document became an expense or an income. |
| `bank_movement.reconciliation_proposed` | Melso proposes a match for a movement. |
| `tax_obligation.due_soon` | A form is due in seven days and is not filed. |
| `tax_return.calculated` | A return was calculated, or its draft changed (new `version`). |
| `tax_return.approved` | A person approved a version. |
| `tax_return.filed` | The return was filed. `filing` holds the reference and receipt. |
| `case.updated` | A case changed state. |
| `case.information_requested` | Melso's professionals need something from the company. |
| `case.approval_requested` | A professional's draft waits for the company's approval. |
| `case.completed` | The case is done; its documents include the receipts. |
| `notification.received` | The administration made a notification available to the company. |
| `certificate.issued` | A certificate was issued. |
| `payroll_run.validated` | A payroll run was validated, with or without issues. |
| `affiliation.accepted` | The TGSS accepted an alta, baja or variación. |
| `affiliation.rejected` | The TGSS rejected it. |
| `contribution_settlement.available` | The TGSS calculated the month's settlement. |
| `contribution_settlement.discrepancy` | It does not match the payroll run. |
| `contribution_settlement.paid` | The settlement was charged. |
| `employment_notification.registered` | The SEPE registered a Contrat@ or Certific\@2 notification. |
| `employment_notification.rejected` | The SEPE rejected it. |


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.