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

# Outgoing webhooks

> Receive signed MARKT order and product events on your HTTPS endpoint. Separate from payment-provider IPN.

Outgoing webhooks push **shop events** to a URL you control. They are **not** payment-provider IPN (Stripe, NOWPayments, PayPal, …). Configure IPN on each payment method; configure outgoing webhooks under **Account → Developers**.

## Events

| Event | When |
| - | - |
| `order.created` | Checkout creates an unpaid/processing order row |
| `order.completed` | Order status becomes `completed` |
| `order.refunded` | Order status becomes `refunded` |
| `product.updated` | A product in the catalog is updated |

A `webhook.test` ping is sent only from **Send test**. It is not a production event.

## Dashboard

On **Account → Developers**:

* Create, edit, enable/disable, rotate, and delete endpoints
* Choose events per webhook
* Inspect delivery logs (status, HTTP code, attempt, error)
* Copy the signing secret **once** at create or rotate

Sensitive create / rotate / delete requires password + email code (or authenticator). The raw secret is never listed again.

## Signature

Every POST includes:

```txt theme={null}
X-Markt-Signature: t=<unix_seconds>,v1=<hex>
X-Markt-Event: order.completed
X-Markt-Delivery: <delivery id>
X-Markt-Webhook-Id: <webhook id>
Content-Type: application/json
User-Agent: Markt-Webhooks/1.0
```

Compute HMAC-SHA256 over `${t}.${rawBody}` using the signing secret (`whsec_…`). Reject timestamps older than 5 minutes. Treat deliveries as **at-least-once**.

## Envelope

```json theme={null}
{
  "id": "evt_…",
  "type": "order.completed",
  "created": 1710000000,
  "storeId": "cm…",
  "data": {}
}
```

`data` is the merchant-safe order or product payload (no payment-provider secrets, no IP intelligence).

## Retries

Failed deliveries (network error or non-2xx) retry with exponential backoff (about 1m → 5m → 30m → 2h → 6h). After **10** consecutive failures the webhook is **auto-disabled**. Re-enable it from Developers after you fix the endpoint.

Retries are processed by:

```txt theme={null}
POST /api/cron/outgoing-webhooks
Authorization: Bearer <CRON_SECRET>
```

## Node example

```js theme={null}
import crypto from 'node:crypto'
import http from 'node:http'

const SECRET = process.env.MARKT_WEBHOOK_SECRET

function verify(header, rawBody) {
  const parts = Object.fromEntries(
    String(header ?? '')
      .split(',')
      .map((part) => {
        const [k, ...rest] = part.trim().split('=')
        return [k, rest.join('=')]
      }),
  )
  const t = Number(parts.t)
  if (!Number.isFinite(t) || Math.abs(Date.now() - t * 1000) > 5 * 60 * 1000) return false
  const expected = crypto.createHmac('sha256', SECRET).update(`${t}.${rawBody}`).digest('hex')
  const a = Buffer.from(expected, 'hex')
  const b = Buffer.from(parts.v1 ?? '', 'hex')
  return a.length === b.length && crypto.timingSafeEqual(a, b)
}

http.createServer((req, res) => {
  const chunks = []
  req.on('data', (c) => chunks.push(c))
  req.on('end', () => {
    const rawBody = Buffer.concat(chunks).toString('utf8')
    if (!verify(req.headers['x-markt-signature'], rawBody)) {
      res.statusCode = 401
      res.end('invalid signature')
      return
    }
    const event = JSON.parse(rawBody)
    console.log(event.type, event.id)
    res.statusCode = 200
    res.end('ok')
  })
}).listen(8787)
```

## cURL (create via Merchant API)

```bash theme={null}
curl -X POST "https://dash.joinmarkt.com/api/merchant/v1/stores/{storeId}/webhooks" \
  -H "Authorization: Bearer mk_live_<your_key>" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Orders to backend",
    "url": "https://example.com/webhooks/markt",
    "events": ["order.created", "order.completed", "order.refunded", "product.updated"]
  }'
```

Store `secret` from the `201` body. Later `GET` responses never include it.

## Related

* [Merchant webhooks API](/api/merchant-webhooks)
* [API keys](/developers/api-keys)
* [Payment-provider IPN](/developers/webhooks)


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