> ## Documentation Index
> Fetch the complete documentation index at: https://docs.accelebit.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Receive Webhook Events from Accelebit Payments

> Set up and verify Accelebit webhook notifications. Includes signature verification, retry behavior, and idempotency patterns in Node.js, Python, and PHP.

Accelebit sends HTTP POST requests to your configured endpoint whenever a payment event occurs on your account. Webhooks let you react to payment completions, failures, and refunds in real time — without polling the API. Events are guaranteed to be delivered at least once, with automatic retries if your endpoint does not respond within 15 seconds.

## How webhooks work

When an event occurs — such as a payment being captured or a refund being initiated — Accelebit delivers a POST request to every endpoint you have registered for that event type. Your server receives a JSON payload alongside a set of verification headers. You confirm receipt by returning a `2xx` response within 15 seconds.

Because delivery is guaranteed at least once, the same event may occasionally arrive more than once. Use the `X-Webhook-Id` header to deduplicate events in your handler.

## Setting up webhooks

<Steps>
  <Step title="Open the Accelebit Dashboard">
    Go to [dashboard.accelebit.com](https://dashboard.accelebit.com) and sign in to your account.
  </Step>

  <Step title="Navigate to the Webhooks section">
    In the left sidebar, select **Developers**, then click **Webhooks**.
  </Step>

  <Step title="Add your endpoint URL">
    Click **Add endpoint** and enter the URL where Accelebit should send events. Your endpoint must use HTTPS in production.
  </Step>

  <Step title="Select the events to receive">
    Choose which event types your endpoint should receive. You can subscribe to individual events or all events on your account.
  </Step>

  <Step title="Copy your webhook signing secret">
    After saving the endpoint, copy the **webhook signing secret**. You will use this to verify that incoming requests originate from Accelebit.
  </Step>
</Steps>

## Webhook payload structure

Every webhook request includes the following HTTP headers:

| Header                | Description                                               |
| --------------------- | --------------------------------------------------------- |
| `Content-Type`        | Always `application/json`                                 |
| `X-Webhook-Signature` | HMAC-SHA256 signature of the raw request body             |
| `X-Webhook-Id`        | Unique delivery identifier, used for deduplication        |
| `X-Webhook-Timestamp` | ISO 8601 timestamp of when Accelebit dispatched the event |

The request body uses this structure:

```json theme={null}
{
  "event": "payment.captured",
  "data": {
    "transactionId": "f47ac10b-58cc-4372-a567-0e02b2c3d479",
    "merchantId": "merchant_abc123",
    "providerId": "everest",
    "status": "captured",
    "amount": 5500,
    "currency": "EUR"
  },
  "timestamp": "2026-04-09T12:01:01.000Z"
}
```

<Note>
  Webhook amounts are in **minor units** (cents). An `amount` of `5500` in `EUR` represents 55.00 EUR. This differs from the REST API, which expresses amounts as decimal strings.
</Note>

## Verifying signatures

Always verify the `X-Webhook-Signature` header before processing any webhook payload. This confirms the request originated from Accelebit and that the body has not been tampered with in transit.

Compute an HMAC-SHA256 digest of the raw request body using your webhook signing secret, then compare the result to the value in `X-Webhook-Signature`. Use a constant-time comparison to prevent timing attacks.

<CodeGroup>
  ```typescript Node.js theme={null}
  import crypto from 'node:crypto';

  function verifyWebhookSignature(
    body: string,
    signature: string,
    secret: string
  ): boolean {
    const expected = crypto
      .createHmac('sha256', secret)
      .update(body)
      .digest('hex');

    return crypto.timingSafeEqual(
      Buffer.from(signature),
      Buffer.from(expected)
    );
  }

  // In your webhook handler:
  app.post('/webhooks/accelebit', (req, res) => {
    const signature = req.headers['x-webhook-signature'] as string;
    const rawBody = req.body; // Must be the raw string, not parsed JSON

    if (!verifyWebhookSignature(rawBody, signature, process.env.WEBHOOK_SECRET!)) {
      return res.status(401).json({ error: 'Invalid signature' });
    }

    const event = JSON.parse(rawBody);
    // Process the event...

    res.status(200).json({ received: true });
  });
  ```

  ```python Python theme={null}
  import hmac
  import hashlib

  def verify_webhook_signature(body: bytes, signature: str, secret: str) -> bool:
      expected = hmac.new(
          secret.encode(),
          body,
          hashlib.sha256
      ).hexdigest()
      return hmac.compare_digest(signature, expected)
  ```

  ```php PHP theme={null}
  function verifyWebhookSignature(string $body, string $signature, string $secret): bool
  {
      $expected = hash_hmac('sha256', $body, $secret);
      return hash_equals($expected, $signature);
  }
  ```
</CodeGroup>

## Responding to webhooks

Your endpoint must return a `2xx` status code within **15 seconds** of receiving a webhook request. If Accelebit does not receive a `2xx` response within that window — whether due to a timeout, a network error, or a non-`2xx` status — the delivery is considered failed and will be retried.

<Warning>
  Process webhooks asynchronously. Return `200` immediately, then handle your business logic in a background job or queue. Long-running synchronous processing risks exceeding the 15-second timeout, causing unnecessary retries.
</Warning>

## Retry behavior

When a delivery fails, Accelebit retries it with exponential backoff across up to seven attempts:

| Attempt | Delay      |
| ------- | ---------- |
| 1       | Immediate  |
| 2       | 1 minute   |
| 3       | 5 minutes  |
| 4       | 30 minutes |
| 5       | 2 hours    |
| 6       | 12 hours   |
| 7       | 24 hours   |

After seven failed attempts, the delivery is marked as permanently failed. You can manually trigger a retry for any failed delivery from the **Webhooks** section of the Dashboard.

## Idempotency

Because webhooks are delivered at least once, your handler must be idempotent — processing the same event twice should produce no unintended side effects. Use the `X-Webhook-Id` header to detect and skip duplicate deliveries:

```typescript theme={null}
const processedWebhooks = new Set<string>();

app.post('/webhooks/accelebit', (req, res) => {
  const webhookId = req.headers['x-webhook-id'] as string;

  if (processedWebhooks.has(webhookId)) {
    return res.status(200).json({ received: true }); // Already processed
  }

  // Process the event...
  processedWebhooks.add(webhookId);

  res.status(200).json({ received: true });
});
```

<Tip>
  In production, store processed webhook IDs in your database or Redis rather than in memory. An in-memory set is lost on every process restart, which would allow duplicates to slip through after a deployment or crash.
</Tip>

## Best practices

* **Always verify signatures** before trusting or acting on any webhook payload.
* **Return 200 immediately** and move business logic to a background job to avoid timeouts.
* **Deduplicate using `X-Webhook-Id`** and persist the ID store across process restarts.
* **Use HTTPS** for your webhook endpoint in production environments.
* **Do not rely solely on webhooks** for critical flows. Before fulfilling an order, confirm payment status directly via `GET /v1/payments/:id`.
* **Log webhook payloads** to support debugging and payment reconciliation.
