# Nomod Webhooks

[Nomod](https://nomod.com) is a UAE payment-links and card-acceptance platform for SMEs. It
sends charge lifecycle webhooks for the **Nomod API** and **Nomod Hosted Checkout** integration
types, delivered through Svix, which implements Standard Webhooks.
Every delivery is an HTTP `POST` with a JSON body and three signature headers: `svix-id`,
`svix-timestamp`, and `svix-signature`. The body is `{type, eventId, objectId, data}`: `type` is
the event name, `eventId` deduplicates repeated deliveries, `objectId` is the charge (`ch_...`)
and `data` is the full charge.

## When to Use This Skill

* How do I receive Nomod webhooks?
* How do I verify Nomod webhook signatures (`svix-signature`)?
* How do I handle `charge.completed`, `charge.failed` or `charge.refunded` events?
* How do I react to `charge.authorised` or `charge.dispute.created`?
* Why is my Nomod webhook signature verification failing, or rejecting Hookdeck retries?

## Verification (core)

Signed content is `{svix-id}.{svix-timestamp}.{raw body}`. Svix signs it with HMAC-SHA256 using
the base64-decoded secret (the part after `whsec_`), base64-encodes the result, and sends one or
more space-separated `v1,<signature>` entries. Use the official `svix` library, which compares in
constant time. Read the `svix-*` headers, and when `svix-id` is absent fall back to the `webhook-*`
headers (`webhook-id`, `webhook-timestamp`, `webhook-signature`), as Hookdeck does.

```javascript
import { Webhook } from 'svix'; // svix 2.x is ESM-only

const prefix = req.headers['svix-id'] ? 'svix' : 'webhook';
const id = req.headers[`${prefix}-id`];
const timestamp = req.headers[`${prefix}-timestamp`];
const signature = req.headers[`${prefix}-signature`];
// rawBody: the raw Buffer/string, never re-serialized JSON
new Webhook(process.env.NOMOD_WEBHOOK_SECRET).verify(rawBody, {
  'svix-id': id, 'svix-timestamp': timestamp, 'svix-signature': signature,
  'webhook-id': id, 'webhook-timestamp': timestamp, 'webhook-signature': signature,
}); // throws on a bad signature or a timestamp outside the window
const event = JSON.parse(rawBody.toString('utf8')); // parse only after verify passes

```

```python
from svix.webhooks import Webhook, WebhookVerificationError

wh = Webhook(os.environ["NOMOD_WEBHOOK_SECRET"])  # "whsec_..."
wh.verify(raw_body, {"svix-id": id, "svix-timestamp": ts, "svix-signature": sig,
                     "webhook-id": id, "webhook-timestamp": ts, "webhook-signature": sig})

```

> **For complete handlers with tests**, see [examples/express/](https://github.com/hookdeck/webhook-skills/tree/main/skills/nomod-webhooks/examples/express/), [examples/nextjs/](https://github.com/hookdeck/webhook-skills/tree/main/skills/nomod-webhooks/examples/nextjs/), [examples/fastapi/](https://github.com/hookdeck/webhook-skills/tree/main/skills/nomod-webhooks/examples/fastapi/).

The signing secret is per webhook, starts with `whsec_`, and is in the dashboard under Settings >
Tools & customisations > Apps & APIs > Webhooks > your webhook > **Signing secret**. Keep the full
`whsec_...` value in `NOMOD_WEBHOOK_SECRET`. Handlers fail closed: with the variable unset they
return 500 instead of accepting anything. Missing headers or a bad signature get a 400. Nomod
tells receivers to reject timestamps more than five minutes from now.

### Hookdeck retries and replays hit the 5-minute window

The `svix` library always enforces a fixed 5-minute timestamp window, and it can't be configured.
Hookdeck retries and replays resend the **original** `svix-timestamp`, so the library rejects any
retry or replay that happens more than 5 minutes after the original delivery. If you verify
behind Hookdeck (or replay old events), use the manual verifier in
[references/verification.md](https://github.com/hookdeck/webhook-skills/blob/main/skills/nomod-webhooks/references/verification.md), where the window can be dropped or
widened. Deduplicate on `eventId` (or `svix-id`) instead of relying on the timestamp.

## Common Event Types

Eight events are documented (the list may not be exhaustive). The name is `type` in the JSON
body (not `data.events[].type`, which is the charge's own history). The examples handle all of these:

| Event | Triggered When |
| --- | --- |
| `charge.created` | A payment object is created and awaiting customer action |
| `charge.authorised` | Funds are reserved on the customer's card, awaiting capture |
| `charge.completed` | Funds are captured and settled to your balance |
| `charge.failed` | The payment was declined at authorisation or capture |
| `charge.cancelled` | The authorisation was voided before capture |
| `charge.refunded` | A full refund was issued |
| `charge.partially_refunded` | A partial refund was issued; the remaining amount stays settled |
| `charge.dispute.created` | A chargeback or dispute was opened by the customer |

See [references/overview.md](https://github.com/hookdeck/webhook-skills/blob/main/skills/nomod-webhooks/references/overview.md) for the payload and delivery rules.

## Environment Variables

```bash
# Signing secret of the webhook (Svix format, starts with whsec_)
NOMOD_WEBHOOK_SECRET=whsec_xxxxx

```

## Local Development

```bash
# Start tunnel (no account needed)
npx hookdeck-cli listen 3000 nomod --path /webhooks/nomod

```

Create the webhook in Settings > Tools & customisations > Apps & APIs > Webhooks (Owners and
Admins only), pick an integration type and the events, and use the tunnel URL. Nomod sends no
challenge. Copy the **Signing secret** into `.env`. See [references/setup.md](https://github.com/hookdeck/webhook-skills/blob/main/skills/nomod-webhooks/references/setup.md).

## Reference Materials

* [references/overview.md](https://github.com/hookdeck/webhook-skills/blob/main/skills/nomod-webhooks/references/overview.md) - Nomod webhook concepts, events, envelope, delivery rules
* [references/setup.md](https://github.com/hookdeck/webhook-skills/blob/main/skills/nomod-webhooks/references/setup.md) - Create a webhook, get and rotate the secret, event logs
* [references/verification.md](https://github.com/hookdeck/webhook-skills/blob/main/skills/nomod-webhooks/references/verification.md) - Svix verification details, header fallback, manual verifier, timestamp window