# Webhooks

Fluense sends signed webhooks for checkout and charge events. Configure one URL per organization.

## Configure

```bash
# Set or replace the URL (owner/admin). The signing secret is generated once and reused.
curl -s -X PUT https://api.fluense.social/orgs/$ORG/webhook \
  -H "authorization: Bearer $SESSION" \
  -H 'content-type: application/json' \
  -d '{"url":"https://example.com/fluense/webhook"}'

# Inspect
curl -s https://api.fluense.social/orgs/$ORG/webhook -H "authorization: Bearer $SESSION"

# Clear
curl -s -X DELETE https://api.fluense.social/orgs/$ORG/webhook -H "authorization: Bearer $SESSION"
```

`PUT` returns the secret (`whsec_…`); `GET` returns `{url, has_secret}` without exposing it.

## Events

| Event | Fires when | Payload |
| --- | --- | --- |
| `checkout.authorized` | A shopper authorizes a checkout | `checkout_id`, `auth_id`, `total_minor`, `currency`, `consumer_id` |
| `charge.captured` | You capture a charge | `checkout_id`, `auth_id`, `mdr_fee`, `buyer_amount`, `deferred_buyer_amount`, `recommender_amount` |
| `charge.voided` | An authorization is voided | `auth_id` |
| `charge.returned` | A refund is processed | `auth_id`, `return_id`, `refund`, `buyer_cut`, `rec_cut`, `award_adjusted` |

## Delivery

Every delivery carries:

| Header | Value |
| --- | --- |
| `x-fluense-event` | Event name, e.g. `charge.captured` |
| `x-fluense-delivery` | Unique delivery id — dedupe on this across retries |
| `x-fluense-signature` | `sha256=<hex HMAC-SHA256 of the raw body with your secret>` |

Any `2xx` marks the delivery delivered. Failures retry with backoff: **60 s, 5 min, 15 min, 1 h, 6 h**, then the delivery is marked `FAILED` (5 attempts).

## Verify a signature

```js
// Node.js
import crypto from 'node:crypto'

const expected = 'sha256=' + crypto
  .createHmac('sha256', process.env.FLUENSE_WEBHOOK_SECRET)
  .update(rawBody) // the exact bytes received, before JSON parsing
  .digest('hex')

if (crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(signature))) {
  // trusted
}
```

```python
# Python
import hashlib, hmac

expected = 'sha256=' + hmac.new(
    secret.encode(), raw_body, hashlib.sha256
).hexdigest()

if hmac.compare_digest(expected, signature):
    pass  # trusted
```

Always verify against the **raw request body**; re-serializing JSON changes the bytes.
