DeriPayDeriPayDocs
DeriPayDeriPayDocs
Merchant dashboardBuild with DeriPay

Getting started

QuickstartAuthenticationEnvironments

Integration guides

Limits and input formatsIdempotency and retriesPayment lifecycleWebhooksTest your integration

API reference

Create a collectionCreate a payoutRetrieve a paymentRead balances
Errors and troubleshooting

Merchant API · v1

Webhooks

Receive signed payment results, verify them, and handle retries.

Configure an endpoint

Open Developers → Webhooks in the merchant dashboard. Enter a public HTTPS URL on port 443, then verify your current password. The URL must be no more than 2048 bytes. URLs cannot contain credentials, a query string, or a fragment.

Save the signing secret when it appears. It is different from your API key. Your receiver must have the secret before processing events.

Private destinations, unsafe DNS results, and redirects are rejected by the sender.

Events

Event typeMeaning
collection.succeededCollection succeeded
collection.failedCollection failed
disbursement.succeededPayout succeeded
disbursement.failedPayout failed

Only terminal results produce payment events. The envelope contains id, type, created_at, and data, which is the payment object.

Illustrative event
{
  "id": "507f1f77bcf86cd799439011:succeeded",
  "type": "collection.succeeded",
  "created_at": "2026-09-05T12:00:10Z",
  "data": {
    "id": "507f1f77bcf86cd799439011",
    "kind": "collection",
    "method": "stk",
    "status": "succeeded",
    "amount_minor": 10000,
    "currency": "KES",
    "fee_minor": 1000,
    "provider_fee_minor": 0,
    "phone": "254712345678",
    "reference": "ORDER-000123",
    "shortcode": "600000",
    "receipt": "EXAMPLE123",
    "created_at": "2026-09-05T12:00:00Z",
    "updated_at": "2026-09-05T12:00:10Z"
  }
}

Amounts, fees, shortcode, and receipt above are examples.

Signature headers

HeaderValue
X-Deripay-Event-IDStable event identifier
X-Deripay-TimestampUnix timestamp in seconds, generated for that delivery attempt
X-Deripay-Signaturev1= followed by the hexadecimal HMAC-SHA256 digest

The signed bytes are timestamp + "." + rawBody. Read the body as bytes before parsing JSON. Re-serializing JSON changes the signature input.

Verify on your server

The following helper uses Node.js built-ins. Reject signatures with timestamps more than five minutes from your server clock.

verify-webhook.mjs
import { createHmac, timingSafeEqual } from "node:crypto";

export function verifyWebhook(rawBody, timestamp, signature, secrets) {
  if (!Buffer.isBuffer(rawBody)) return false;
  if (!/^\d{1,12}$/.test(timestamp ?? "")) return false;
  if (!/^v1=[a-f0-9]{64}$/.test(signature ?? "")) return false;
  const seconds = Number(timestamp);
  if (Math.abs(Date.now() / 1000 - seconds) > 300) return false;

  const received = Buffer.from(signature.slice(3), "hex");
  return secrets.filter(Boolean).some((secret) => {
    const expected = createHmac("sha256", secret)
      .update(timestamp + ".")
      .update(rawBody)
      .digest();
    return timingSafeEqual(expected, received);
  });
}

After verification, parse the body. Verify the envelope's id matches X-Deripay-Event-ID. Store and deduplicate that signed body ID, not an unverified header. Validate the event type and payment data before using them.

Acknowledge durably

Persist the event, or durably enqueue it, before returning a 2xx response. Use a unique constraint on the event ID so duplicates do not fulfil an order twice.

The sender sends an HTTP POST with Content-Type: application/json. It waits up to 8 seconds for response headers and has a 12-second overall request timeout. Acknowledge promptly after durable storage; process business actions asynchronously.

The sender treats non-2xx responses and timeouts as failed attempts. It uses automatic retries, up to 12 attempts per delivery cycle. Administrative replay can start another cycle. Do not assume delivery order or exactly-once transport.

Retries are scheduled after 2, 4, 8, 16, 32, 64, 128, 256, 512, 1024, and 1024 seconds following failed attempts. Worker scheduling and outages can delay actual delivery. An HTTP Retry-After returned by your receiver does not change this schedule.

Delivery stateMeaning
pendingWaiting for an attempt or retry
processingA worker has claimed the delivery
deliveredReceiver returned 2xx
deadAutomatic attempts exhausted; administrative review is required
disabledNo endpoint or signing secret was configured when the event was recorded

These are delivery states, separate from the payment status. A failed webhook does not mean the payment failed.

Rotate or disable

Updating an existing URL without requesting rotation retains its signing secret. First-time configuration or explicit rotation generates a secret shown once. Generating a new signing secret applies to future events. Already queued events retain their original encrypted signing secret and endpoint URL. Keep the previous secret available for verification until those deliveries finish.

Clearing the endpoint disables new notifications. It does not cancel existing queued deliveries. Events recorded while no endpoint was configured appear as Not configured in delivery history and are not automatically backfilled when you add an endpoint.

Troubleshoot delivery

Check Developers → Webhooks → Delivery history for the event ID, state, and attempt count. Inspect receiver logs for that event ID without logging keys or signing secrets. A final failed delivery needs administrative review; polling the payment remains available.

Payment lifecycle

Interpret every payment state and confirm the final outcome.

Test your integration

Verify authentication, retries, payment outcomes, and webhook handling before live use.

On this page

Configure an endpointEventsSignature headersVerify on your serverAcknowledge durablyRotate or disableTroubleshoot delivery