Skip to main content

Webhooks

When a payment happens, successfully or unsuccessfully -- we'll send a webhook to your server to let you know. Webhooks are the best way to stay on top of what's happening with your payments, and you should use them as your source of truth for payment status.

Setting things up

You set up webhook subscriptions through Basecamp. For each subscription you need:

  • A target URL: The HTTPS endpoint on your server where we should send events.
  • An HMAC secret: A shared secret we use to sign payloads so you can verify they came from us.

Your target URL needs to be publicly reachable over HTTPS. We can't send webhooks to private IPs - your webhook url must be accessible via the public internet.

What events you'll get

Here are the payment events that trigger a webhook:

EventWhat happened
authorisationA payment was authorised (or turned down).
captureA payment was captured.
cancellationA payment was cancelled.
cancel.or.refundA reversal went through (either a cancel or a refund).
refundA refund was processed.
refund.with.dataA refund was processed, with extra data attached.
chargebackA chargeback came in.
notification.of.fraudA fraud notification was flagged.

Payments made through the ECOM API have "paymentSource": "Ecom" in their metadata, so you can tell them apart from other payment types.

What a webhook looks like

We send each webhook as an HTTP POST to your target URL with a JSON body.

Headers we send:

HeaderWhat it is
Content-Typeapplication/json
User-AgentYetipay-Dispatch/1.0
X-Webhook-IdA unique ID for this particular webhook delivery.
X-Webhook-TimestampUnix timestamp (in seconds) of when we signed the payload.
X-Webhook-HMAC-SignatureThe HMAC-SHA256 signature. Looks like sha256={hex}.
X-Webhook-Delivery-AttemptWhich attempt this is (starts at 1).
X-Webhook-Payload-VersionThe payload format version.

Example payload:

{
"live": true,
"notificationItems": [
{
"NotificationRequestItem": {
"eventCode": "AUTHORISATION",
"success": "true",
"pspReference": "8835612345678901",
"merchantReference": "order-12345",
"amount": {
"value": 2500,
"currency": "GBP"
},
"paymentMethod": "visa",
"additionalData": {
"paymentSource": "Ecom"
}
}
}
]
}

Checking that a webhook is genuine

Before you do anything with a webhook, make sure it actually came from us. We sign every payload with your HMAC secret so you can verify it hasn't been tampered with.

How to verify

  1. Grab the X-Webhook-Timestamp and X-Webhook-HMAC-Signature headers.
  2. Build the signed string: {timestamp}.{raw request body}.
  3. Compute the HMAC-SHA256 of that string using your HMAC secret.
  4. Compare it with the signature in the header (after removing the sha256= prefix). Use a constant-time comparison to be safe.

Node.js example

const crypto = require('crypto');

function isWebhookGenuine(rawBody, headers, hmacSecret) {
const timestamp = headers['x-webhook-timestamp'];
const receivedSignature = headers['x-webhook-hmac-signature'];

if (!timestamp || !receivedSignature) {
return false;
}

const signedPayload = `${timestamp}.${rawBody}`;
const expectedSignature = crypto.createHmac('sha256', hmacSecret).update(signedPayload, 'utf8').digest('hex');

const expected = `sha256=${expectedSignature}`;
return crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(receivedSignature));
}

Python example

import hmac
import hashlib

def is_webhook_genuine(raw_body: str, headers: dict, hmac_secret: str) -> bool:
timestamp = headers.get("x-webhook-timestamp")
received_signature = headers.get("x-webhook-hmac-signature")

if not timestamp or not received_signature:
return False

signed_payload = f"{timestamp}.{raw_body}"
expected = hmac.new(
hmac_secret.encode("utf-8"),
signed_payload.encode("utf-8"),
hashlib.sha256,
).hexdigest()

return hmac.compare_digest(f"sha256={expected}", received_signature)

If we can't reach you

If your server doesn't come back with a 2xx status code, we'll try again:

  • Up to 5 attempts in total.
  • 15-second timeout on each attempt.

The X-Webhook-Delivery-Attempt header tells you which attempt you're on. If all 5 fail, we stop trying - sorry.

Tips

  • Always check the signature before doing anything with the payload. See Checking that a webhook is genuine.
  • Reply quickly. If you need to do something slow, pop the event onto a queue and return 200 straight away. If your endpoint takes too long, we might time out and retry -- which means you could end up processing the same event twice.
  • Handle duplicates. Because of retries, you might get the same event more than once. Use the X-Webhook-Id or pspReference to spot duplicates.
  • Treat webhooks as the source of truth. The response you get from a payment request tells you the initial result, but the webhook confirms what actually happened. Always reconcile your records against webhooks.