# Praxis Webhooks

Praxis (Praxis Tech, the "Cashier" payment orchestration platform) signs every
outbound webhook with a SHA-384 hex digest in the lowercase
`gt-authentication` header. This is not an HMAC and not Standard
Webhooks: Praxis takes a fixed, per-webhook-type list of field values in
the documented order, concatenates them into one string, appends your Merchant
Secret, and hashes the result with `sha384`. Your endpoint must reply `200`
with a `{ "status": 0, ... }` body and sign that acknowledgement with the
`external-request-signature` header.

## When to Use This Skill

* How do I receive Praxis / Praxis Tech / Cashier webhooks?
* How do I verify the `gt-authentication` signature on a Praxis webhook?
* Why is my Praxis SHA-384 signature verification failing?
* How do I sign the Praxis acknowledgement (`external-request-signature`)?
* How do I handle Payment Notification `transaction_status` (pending, approved, rejected, error)?
* How do I handle a Praxis Subscription Notification `event`?

## Verification (core)

Concatenate the documented field values in order (do not alphabetize —
that is only for the general API-request signature), append the Merchant Secret,
then `sha384` (hex). Compare to the `gt-authentication` header.

```javascript
const crypto = require('crypto');

const PAYMENT_FIELDS = ['merchant_id', 'application_key', 'timestamp', 'customer.customer_token',
  'session.order_id', 'transaction.tid', 'transaction.currency', 'transaction.amount',
  'transaction.conversion_rate', 'transaction.processed_currency', 'transaction.processed_amount'];
const SUBSCRIPTION_FIELDS = ['event', 'merchant_id', 'application_key', 'cid', 'plan_id',
  'subscription_id', 'subscription_status', 'timestamp'];

const at = (o, p) => p.split('.').reduce((x, k) => (x == null ? undefined : x[k]), o);

// Subscription Notifications carry an `event` field; Payment Notifications do not.
function verifyPraxis(body, headerSig, merchantSecret) {
  const fields = body.event ? SUBSCRIPTION_FIELDS : PAYMENT_FIELDS;
  const data = fields.map((p) => String(at(body, p) ?? '')).join('') + merchantSecret;
  const expected = crypto.createHash('sha384').update(data, 'utf8').digest('hex');
  const a = Buffer.from(String(headerSig || ''), 'utf8');
  const b = Buffer.from(expected, 'utf8');
  return a.length === b.length && crypto.timingSafeEqual(a, b); // timing-safe
}

```

Sign the acknowledgement you return (`sha384` of `status + timestamp + secret`, sent in the `external-request-signature` header):

```javascript
const status = 0;
const timestamp = Math.floor(Date.now() / 1000);
const ackSig = crypto.createHash('sha384')
  .update(`${status}${timestamp}${merchantSecret}`, 'utf8').digest('hex');
// res.set('external-request-signature', ackSig).status(200).json({ status, timestamp });

```

> Parse before verify (deliberate exception): the signature covers field
> values, not the raw body, so you must parse the JSON to rebuild the signed
> string. This is the opposite of HMAC-over-raw-body providers. See
> [references/verification.md](https://github.com/hookdeck/webhook-skills/blob/main/skills/praxis-webhooks/references/verification.md) for the number-vs-string gotcha.

> For complete handlers with signature verification, event dispatch, the signed
> acknowledgement, and tests, see:
> 
> * [examples/express/](https://github.com/hookdeck/webhook-skills/tree/main/skills/praxis-webhooks/examples/express/)
> * [examples/nextjs/](https://github.com/hookdeck/webhook-skills/tree/main/skills/praxis-webhooks/examples/nextjs/)
> * [examples/fastapi/](https://github.com/hookdeck/webhook-skills/tree/main/skills/praxis-webhooks/examples/fastapi/)

## Common Event Types

Payment Notification — no event-name field; identified by the
`transaction.transaction_status` value:

| `transaction_status` | Meaning |
| --- | --- |
| `initialized` | Transaction created |
| `pending` | Awaiting completion |
| `approved` | Transaction approved / funds captured |
| `rejected` | Transaction declined |
| `error` | Processing error |

Subscription Notification — identified by the explicit `event` field,
carrying `subscription_status`, `subscription_id`, `plan_id`, and `cid`:

| `event` | Fires When |
| --- | --- |
| `SubscriptionCreated` | A subscription is created |
| `SubscriptionActivated` | A subscription becomes active |
| `SubscriptionDeactivated` | A subscription is deactivated |
| `SubscriptionExpired` | A subscription expires |
| `SubscriptionCanceled` | A subscription is canceled |
| `PaymentAttemptApproved` | A recurring charge attempt is approved |
| `PaymentAttemptFailed` | A recurring charge attempt fails |
| `PaymentSucceeded` | A subscription payment succeeds |
| `PaymentFailed` | A subscription payment fails |
| `PaymentManuallyPaid` | A payment is marked manually paid |
| `PaymentRefundSucceeded` | A refund succeeds |
| `PaymentRefundFailed` | A refund fails |

`subscription_status` is one of `active`, `inactive`, `expired`, `canceled`.
Confirm the enabled values for your program in the
[Praxis webhook docs](https://docs.praxis.tech/reference/webhooks).

## Environment Variables

```bash
# The Merchant Secret from your Praxis merchant configuration. Used both to
# verify inbound gt-authentication signatures and to sign your acknowledgement.
PRAXIS_MERCHANT_SECRET=your_merchant_secret

```

## Signature Header Reference

| Direction | Header | Content |
| --- | --- | --- |
| Inbound (Praxis → you) | `gt-authentication` | `sha384(field_values + merchant_secret)`, 96-char lowercase hex |
| Outbound (your ACK → Praxis) | `external-request-signature` | `sha384(status + timestamp + merchant_secret)` |

## Local Development

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

```

## Reference Materials

* [references/overview.md](https://github.com/hookdeck/webhook-skills/blob/main/skills/praxis-webhooks/references/overview.md) - What Praxis webhooks are, notification types, payload shape
* [references/setup.md](https://github.com/hookdeck/webhook-skills/blob/main/skills/praxis-webhooks/references/setup.md) - Merchant configuration, notification URLs, the Merchant Secret
* [references/verification.md](https://github.com/hookdeck/webhook-skills/blob/main/skills/praxis-webhooks/references/verification.md) - SHA-384 field-value signing, ACK signing, and gotchas