Webhooks

Receive HTTPS POSTs when passes are created, updated, revoked or when messages are sent. Webhook payloads are HMAC-SHA256 signed.

Event types

eventFired when
pass.createdPOST /passes returns 202.
pass.updatedPATCH /passes/{id} returns 202.
pass.revokedDELETE /passes/{id} returns 202.
message.sentPOST /messages returns 202.
job.completed(Reserved โ€” worker-side trigger pending.)
job.failed(Reserved โ€” worker-side trigger pending.)
batch.completed(Reserved โ€” worker-side trigger pending.)

Register a Webhook

POST /api/v1/webhooks
POST /api/v1/webhooks
{
  "url":     "https://yourdomain.com/wallet-events",
  "events":  ["pass.created", "pass.updated"],
  "secret":  "whsec_your_secret_here"
}
โ— 201 Created
{
  "id":            "...",
  "url":           "https://yourdomain.com/wallet-events",
  "events":        ["pass.created", "pass.updated"],
  "is_active":     true,
  "secret_prefix": "whse",
  "created_at":    "2026-05-18T10:00:00Z"
}

URL must be HTTPS only. The full secret is never returned โ€” only its 4-char prefix.

Signature verification

Every webhook delivery carries an HMAC-SHA256 signature of the raw request body (computed with the secret you supplied):

Header X-WalletPlatform-Signature: sha256={hex}
Header X-WalletPlatform-Event: pass.created
Header X-WalletPlatform-Delivery: {delivery-id}
using var hmac = new HMACSHA256(Encoding.UTF8.GetBytes(secret));
var hash = hmac.ComputeHash(Encoding.UTF8.GetBytes(rawBody));
var expected = Convert.ToHexString(hash).ToLowerInvariant();
var header = request.Headers["X-WalletPlatform-Signature"]
    .ToString().Split('=', 2)[1];

if (!CryptographicOperations.FixedTimeEquals(
        Encoding.UTF8.GetBytes(expected),
        Encoding.UTF8.GetBytes(header)))
{
    return Unauthorized();
}
import crypto from 'node:crypto';

const expected = crypto
  .createHmac('sha256', secret)
  .update(rawBody)
  .digest('hex');

const header = req.headers['x-walletplatform-signature']
  .split('=', 2)[1];

if (!crypto.timingSafeEqual(
      Buffer.from(expected, 'hex'),
      Buffer.from(header,   'hex'))) {
  return res.status(401).end();
}
import hmac, hashlib

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

header = request.headers['X-WalletPlatform-Signature'].split('=', 1)[1]

if not hmac.compare_digest(expected, header):
    return Response(status=401)
$expected = hash_hmac('sha256', $rawBody, $secret);
$header   = explode('=', $_SERVER['HTTP_X_WALLETPLATFORM_SIGNATURE'], 2)[1];

if (!hash_equals($expected, $header)) {
    http_response_code(401);
    exit;
}

Delivery retries

A 2xx response is required within 30 seconds. Failures (non-2xx, timeout, exception) trigger exponential-backoff retries:

After 5 failed attempts the delivery is dead-lettered. Inspect the history via:

GET /api/v1/webhooks/{id}/deliveries

Other endpoints

GET /api/v1/webhooks
GET /api/v1/webhooks/{id}
PATCH /api/v1/webhooks/{id}
DELETE /api/v1/webhooks/{id}