Gareth Wilson Gareth Wilson

Guide to Ascend Webhooks: Features and Best Practices

Published


Ascend webhooks notify your application about payment activity: an invoice is paid, a payout is sent, a refund is processed. If you're building on Ascend for insurance payments or premium financing, webhooks are how you react to these events without polling.

This guide covers how Ascend webhooks work, the events you'll handle, how to verify the X-Ascend-Signature, and the best practices for production.

What are Ascend webhooks?

Ascend webhooks are JSON POSTs delivered over HTTPS to a URL you register with Ascend. Each is signed with an X-Ascend-Signature header formatted t=<timestamp>,v1=<hex>. The signature is an HMAC-SHA256 (hex) over <timestamp> + ":" + raw_body, keyed with your webhook secret. The same timestamp also arrives in the X-Ascend-Request-Timestamp header. This is a custom scheme, not Svix or Standard Webhooks, so don't reach for a Standard Webhooks library.

Ascend webhook features

FeatureDetails
ConfigurationManual: email Ascend support with your org, environment, events, and URL (no self-serve dashboard)
Signature headerX-Ascend-Signature, formatted t=<ts>,v1=<hex>
Signature schemeHMAC-SHA256 (hex) over <timestamp>:<raw_body>, keyed with the webhook secret
TimestampAlso sent in X-Ascend-Request-Timestamp
Payload{ id, type, data }
AcknowledgementReturn HTTP 200
Retry / toleranceNot documented
SDKNone

Common events

Ascend event names follow a noun.verb pattern. One spelling detail is worth pinning: refunds use the British cancelled while payouts use the American canceled.

EventFires when
invoice.paidAn invoice is paid
payout.paidA payout is paid
payout.canceledA payout is canceled (American spelling)
refund.cancelledA refund is cancelled (British spelling)

Match the exact spelling per event rather than assuming one convention across the board.

Setting up Ascend webhooks

Registration is manual. Email Ascend support with your organization, environment, the events you want, and your endpoint URL. There's no self-serve dashboard, so keep a record of what you registered. Ascend provides the webhook secret you use for verification.

Securing Ascend webhooks

The X-Ascend-Signature header is t=<ts>,v1=<hex>. To verify, build <timestamp> + ":" + raw_body, compute an HMAC-SHA256 with your webhook secret, hex-encode it, and compare against the v1 value. Verify against the raw body, re-serializing the JSON changes the bytes and breaks the match.

const crypto = require("crypto");

const SECRET = process.env.ASCEND_WEBHOOK_SECRET;

function verify(rawBody, signatureHeader) {
  const params = Object.fromEntries(
    (signatureHeader || "").split(",").map((kv) => kv.split("="))
  );
  const timestamp = params.t;
  const message = `${timestamp}:${rawBody.toString()}`;
  const expected = crypto.createHmac("sha256", SECRET).update(message).digest("hex");
  const a = Buffer.from(expected);
  const b = Buffer.from(params.v1 || "");
  return a.length === b.length && crypto.timingSafeEqual(a, b);
}

app.post("/webhook", express.raw({ type: "application/json" }), (req, res) => {
  if (!verify(req.body, req.headers["x-ascend-signature"])) {
    return res.sendStatus(401);
  }

  res.sendStatus(200); // acknowledge fast
  processQueue.add(JSON.parse(req.body)); // branch on type, async
});

The same check in Python:

import hashlib
import hmac
import os

SECRET = os.environ["ASCEND_WEBHOOK_SECRET"].encode()


def verify(raw_body: bytes, signature_header: str) -> bool:
    params = dict(
        kv.split("=", 1) for kv in (signature_header or "").split(",") if "=" in kv
    )
    timestamp = params.get("t", "")
    message = (timestamp + ":").encode() + raw_body
    expected = hmac.new(SECRET, message, hashlib.sha256).hexdigest()
    return hmac.compare_digest(expected, params.get("v1", ""))

Ascend webhook limitations and pain points

The signed message is timestamp:body, and it's not Standard Webhooks

The Problem: The HMAC is over <timestamp> + ":" + raw_body, and the header format is Stripe-like but the scheme is Ascend's own. Reaching for a Svix or Standard Webhooks verifier fails, and so does signing the body alone.

Why It Happens: Ascend binds the timestamp into the signed message with a colon separator and ships a custom format.

Workarounds:

  • Build timestamp + ":" + body, HMAC-SHA256, hex, and compare against the v1 value over the raw body.

How Hookdeck Can Help: Hookdeck verifies the signature at the edge with the right scheme, so your app receives pre-verified events without hand-rolling the format.

The spelling differs per event

The Problem: Refunds use cancelled (British) and payouts use canceled (American). A handler keyed to one spelling silently misses the other.

Why It Happens: The event catalog isn't spelled consistently.

Workarounds:

  • Use the exact per-event spelling, and handle unknown types defensively.

How Hookdeck Can Help: Hookdeck routes on the exact type value you receive, so a spelling mismatch never drops an event.

Registration is manual

The Problem: There's no dashboard. You register by email, so it's easy to lose track of which events and URLs are live.

Why It Happens: Ascend onboards webhooks through support.

Workarounds:

  • Keep your own record of registered events and endpoints, and re-confirm after changes.

How Hookdeck Can Help: Hookdeck gives you a single managed endpoint and a record of every event that arrives, so you can see what's actually being delivered.

Retry and tolerance are undocumented

The Problem: Ascend doesn't document retry behavior or a timestamp tolerance, so you can't rely on assumptions about redelivery or replay windows.

Why It Happens: The behavior isn't published.

Workarounds:

  • Make handlers idempotent, and enforce your own freshness window on the timestamp.

How Hookdeck Can Help: Hookdeck retries with its own policy and records every attempt, so delivery doesn't depend on undocumented provider behavior.

Best practices

Verify over timestamp:body with the webhook secret

Build <timestamp>:<raw_body>, HMAC-SHA256 with your secret, hex, and compare against v1 in constant time.

Enforce your own freshness window

Ascend doesn't document a tolerance, so compare X-Ascend-Request-Timestamp against your own window to guard against replay.

Return 200 and process asynchronously

Acknowledge quickly and defer work to a queue. See why to process webhooks asynchronously.

Make handlers idempotent

Retry behavior is undocumented, so dedupe on the payload id and make processing safe to repeat.

Make Ascend webhooks production-ready

Hookdeck verifies X-Ascend-Signature, deduplicates, and durably queues every event

Conclusion

Ascend webhooks are verified with an X-Ascend-Signature HMAC-SHA256 over <timestamp>:<raw_body>, keyed with your webhook secret, with the timestamp echoed in X-Ascend-Request-Timestamp. It's a custom scheme, not Standard Webhooks. Verify over the raw body, enforce your own freshness window, match event spellings exactly, and make handlers idempotent.

Hookdeck verifies the signature, deduplicates, and durably queues every event at the edge, so your app only ever processes verified, unique events.

Get started with Hookdeck for free and handle Ascend webhooks reliably in minutes.


Gareth Wilson

Gareth Wilson

Product Marketing

Multi-time founding marketer, Gareth is PMM at Hookdeck and author of the newsletter, Community Inc.