Agent skill

Justt Webhooks Skill

Receive and verify Justt webhooks, which follow the Standard Webhooks specification. Use when setting up a Justt chargeback webhook endpoint, getting the whsec_ signing secret, debugging webhook-id / webhook-timestamp / webhook-signature verification, or handling events like chargeback.created, chargeback.status.updated, pre_chargeback_alert.received and subscription.cancel_immediately.failed.

Install this skill

npx skills add hookdeck/webhook-skills --skill justt-webhooks


Justt manages chargebacks and automates disputes for merchants. Its webhooks report chargeback, pre-chargeback alert and CRM-subscription-cancellation events as a signed JSON POST to each endpoint you add in the Justt webhooks dashboard. Signatures follow Standard Webhooks, so each delivery carries webhook-id, webhook-timestamp and webhook-signature headers. Webhooks are not available to every Justt customer yet. The dashboard, message ids and retry/recovery tooling are Svix's.

When to Use This Skill

  • How do I receive Justt webhooks?
  • How do I verify a Justt webhook signature (webhook-signature)?
  • Where do I find my Justt webhook signing secret (whsec_...)?
  • How do I handle chargeback.created, chargeback.status.updated or pre_chargeback_alert.received events?
  • Why is my Justt webhook verification failing, or failing only on retries and replays?

Verification (core)

HeaderValue
webhook-idmsg_...; identifies the message and is stable across retries. Your idempotency key (the body has no event id)
webhook-timestampUnix seconds
webhook-signatureOne or more space-separated v1,<base64> entries

Signed content is {webhook-id}.{webhook-timestamp}.{raw body}, HMAC-SHA256, base64 output. The key is the base64-decoded part of the whsec_... secret after the prefix. Compare against every v1, entry; ignore other versions. If webhook-id is absent, read svix-id, svix-timestamp and svix-signature instead, as Hookdeck does. The examples use the standardwebhooks library (npm and PyPI):

const { Webhook } = require('standardwebhooks');

// rawBody: exact request body (Buffer or string), NOT re-serialised JSON
function verifyJustt(rawBody, headers, secret = process.env.JUSTT_WEBHOOK_SECRET) {
  const p = headers['webhook-id'] ? 'webhook' : 'svix'; // fall back to the svix-* names
  new Webhook(secret).verify(rawBody, {
    'webhook-id': headers[`${p}-id`],
    'webhook-timestamp': headers[`${p}-timestamp`],
    'webhook-signature': headers[`${p}-signature`],
  }, { jsonParse: false }); // throws on a bad signature or stale timestamp → respond 401
}

The library always enforces a fixed 5-minute timestamp window. Hookdeck retries and replays resend the original webhook-timestamp, so the library rejects them once they are more than 5 minutes old. If you receive through Hookdeck and need late retries to pass, use the manual verifier in references/verification.md, which can drop or widen the window. In Express, read the body with express.raw({ type: '*/*' }) so any content type verifies, and parse JSON only after the check.

For complete handlers with tests, see examples/express/, examples/nextjs/, examples/fastapi/.

Event Type and Payload

Every body is the same envelope, and the event name is the top-level type:

{
  "type": "chargeback.created",
  "timestamp": "2022-03-15T09:39:24.514Z",
  "data": { "id": "04318fb0-e4ff-4712-972e-4db99b3fb23c", "chargebackId": "QRZ54SXB4K6TF232", "psp": "stripe", "status": "waiting_for_data" }
}

timestamp is when the event occurred (ISO 8601). data is the event-specific object. The signature page's code sample uses a ping body with event_type instead; that is a sample, not one of the reference payloads, so route on type.

Common Event Types

EventFired when
chargeback.createdA new chargeback is received from a connected PSP
chargeback.updatedA chargeback is updated
chargeback.status.updatedA chargeback status update is received
chargeback.evidence.submittedEvidence is submitted to the PSP (data.status is succeeded or failed)
chargeback.acceptedA chargeback acceptance is confirmed by the PSP (data.status is succeeded or failed)
pre_chargeback_alert.receivedA pre-chargeback alert arrives from CDRN, RDR or Ethoca
pre_chargeback_alert.refund.succeededThe transaction linked to a refunded alert was refunded through the PSP
pre_chargeback_alert.refund.failedProcessing a refund alert failed
pre_chargeback_alert.transaction_matchedAn alert was matched to its transaction (opt-in, enabled by Justt per account)
subscription.cancel_immediately.successA CRM subscription (for example Chargebee or Recurly) was cancelled after a refund
subscription.cancel_immediately.failedThat cancellation failed (data.failureCode, data.failureReason)

These 11 are the documented events, and the examples route all of them (the list may not be exhaustive: ignore types you don't recognise). See references/overview.md.

Delivery Semantics

Return a 2xx within 15 seconds (the reference spec says 30), then process asynchronously. 4xx responses other than 410, 5xx responses, timeouts and connection failures are retried on an 8-attempt schedule (immediately, then after 5 s, 5 min, 30 min, 2 h, 5 h, 10 h and 10 h, about 27.6 hours in all). 410 Gone is permanent and is not retried. An endpoint that fails for 5 consecutive days is disabled. Deduplicate on webhook-id. Return 400 for missing headers and 401 for a bad signature; handlers fail closed (500) when JUSTT_WEBHOOK_SECRET is unset.

Environment Variables

JUSTT_WEBHOOK_SECRET=whsec_xxxxx   # full "Signing Secret" from the endpoint's settings; handlers fail closed without it

Each endpoint has its own secret. The docs never print the prefix; the sample base64-decodes the part after _, which matches whsec_<base64>.

Local Development

# Express / Next.js (use 8000 for FastAPI). No install or account required.
npx hookdeck-cli listen 3000 justt --path /webhooks/justt

Set the printed URL as the endpoint URL in the Justt webhooks dashboard, then use the endpoint's Testing tab to send a signed example.

Reference Materials


Repository

hookdeck/webhook-skills

v0.1.0 · MIT · Updated Nov 12, 2026

View on GitHub →