Theme
Webhooks
Webhooks are how digid pay tells your server what happened. Register an HTTPS endpoint, subscribe to events, verify signatures, and reconcile — never fulfil an order on a client callback alone.
Register an endpoint
bash
curl https://api.digid.cc/v1/webhook_endpoints \
-u sk_live_...: \
-H "Content-Type: application/json" \
-d '{
"url": "https://api.example.com/digidpay/webhooks",
"events": ["payment_intent.succeeded", "payment_intent.payment_failed"]
}'The response includes your signing secret — shown once. Keep it server-side.
Event catalog
| Event | Meaning |
|---|---|
payment_intent.processing | Confirmed, authorising. |
payment_intent.succeeded | Authorised and captured. Fulfil the order. |
payment_intent.payment_failed | Declined / failed. |
payment_intent.cancelled | Cancelled before completion. |
payment_intent.requires_action | SCA challenge surfaced (informational). |
payment_intent.requires_approval | Agent intent awaiting human approval. |
refund.created | Refund request recorded. |
refund.succeeded | Refund completed. |
refund.failed | Refund rejected. |
Verify signatures (HMAC)
Every delivery carries a Digidpay-Signature header:
Digidpay-Signature: t=1725794400,v1=9a8f...b2Verification: build the signed payload as {timestamp}.{body} where body is the raw request body, compute HMAC-SHA256 with your endpoint secret, and compare v1.
python
import hashlib, hmac, json, time
SECRET = "whsec_..." # your endpoint secret
def verify(raw_body: bytes, header: str) -> bool:
parts = dict(p.split("=", 1) for p in header.split(","))
ts, sig = parts["t"], parts["v1"]
if abs(int(ts) - time.time()) > 300: # replay window
return False
expected = hmac.new(SECRET.encode(), f"{ts}.".encode() + raw_body, hashlib.sha256).hexdigest()
return hmac.compare_digest(expected, sig)javascript
import { createHmac, timingSafeEqual } from 'node:crypto'
const SECRET = 'whsec_...'
export function verify(rawBody, header) {
const parts = Object.fromEntries(header.split(',').map(p => p.split('=')))
const { t: ts, v1: sig } = parts
if (Math.abs(Date.now() / 1000 - Number(ts)) > 300) return false
const expected = createHmac('sha256', SECRET).update(`${ts}.`).update(rawBody).digest('hex')
const a = Buffer.from(expected), b = Buffer.from(sig)
return a.length === b.length && timingSafeEqual(a, b)
}bash
# Verify with openssl (header values filled from the delivery)
payload="$DIGIDPAY_BODY" # raw request body
ts="1725794400"
sig="9a8f..." # v1 value from Digidpay-Signature
computed=$(printf '%s.%s' "$ts" "$payload" | openssl dgst -sha256 -hmac "whsec_..." | awk '{print $2}')
[ "$computed" = "$sig" ] && echo VALID || echo INVALIDIdempotent processing
Deliveries are at-least-once and may retry. Processing must be idempotent — key on the event id and ignore duplicates:
python
seen = set()
def handle(event_id):
if event_id in seen:
return
seen.add(event_id)
# fulfil / update orderRetries & backoff
- Deliveries retry with exponential backoff until acknowledged (
2xx) or the retry budget is exhausted. - Always return
2xxpromptly once you have accepted the event.
Manage endpoints
bash
curl https://api.digid.cc/v1/webhook_endpoints -u sk_live_...: # list
curl https://api.digid.cc/v1/webhook_endpoints/we_1Ef... -u sk_live_...: # retrieve
curl -X POST https://api.digid.cc/v1/webhook_endpoints/we_1Ef... \
-u sk_live_...: -H "Content-Type: application/json" -d '{"enabled": false}' # update
curl -X DELETE https://api.digid.cc/v1/webhook_endpoints/we_1Ef... -u sk_live_...: # deleteDelivery log & replay
Every delivery is logged and retained 12 months. Inspect failures and replay from the dashboard or the API:
bash
curl "https://api.digid.cc/v1/webhook_endpoints/we_1Ef.../deliveries" -u sk_live_...:
curl -X POST "https://api.digid.cc/v1/webhook_endpoints/we_1Ef.../deliveries/dlv_.../replay" \
-u sk_live_...:Signature key rotation
Rotate an endpoint's signing secret by updating the endpoint, capturing the new secret once, and updating your verifier. Keep the old secret verifying during a short overlap if deliveries may still be in flight.