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

# Webhooks

> The inbound webhook contract — the routes that exist, the signature scheme per source, the raw-body requirement, and replay behaviour.

Droplinked **receives** webhooks. This page is the contract for configuring a provider's
dashboard to point at us, and for understanding what we do with the event.

> [!WARNING]
>
> Every path below is mounted on `https://apiv3.droplinked.com`. There is **no**
> `/webhook/stripe` route — a `POST` to it returns `404`, so a Stripe endpoint configured
> there silently fails every event. Earlier revisions of these docs named that path; the
> real Stripe mounts are listed below.

## Payment-provider routes

| Source | Route | Authentication |
| - | - | - |
| Stripe — account onboarding | `POST /stripe/account-onboarding/webhook` | `stripe-signature` header, verified over the **raw body** |
| Stripe — balance | `POST /stripe/balance/webhook` | `stripe-signature` header, raw body |
| Stripe — Connect | `POST /stripe/connect/webhook` | `stripe-signature` header, raw body |
| Stripe — ACP (agentic) | `POST /stripe/acp/webhook` | `stripe-signature` header, raw body |
| PSP-agnostic normalised callback | `POST /payment/webhook/generic` | `integration-api-key` **plus** HMAC body-binding |
| Paymob | `POST /payment/webhook/paymob` | HMAC supplied as the `?hmac=` query parameter, verified server-side |
| MamoPay | `POST /mamopay-connect/webhook/mamopay` | static credential echoed in `Authorization` |
| Telr | `POST /telr/webhook` | Telr transaction-advice signature over the canonical field set |
| AzamPay | `POST /azampay-connect/webhook/azampay` | provider credential |
| Bonum | `POST /bonum/webhook` | provider credential |
| Droplinked subscription billing | `POST /subscription/webhook` | Stripe signature |

PayPal does **not** post directly to the Core API. PayPal events are received and
signature-verified by Integration Services (`paypal-transmission-sig` + the configured
webhook id), which then forwards a normalised event to `POST /payment/webhook/generic`.

## Fulfillment routes

| Source | Route |
| - | - |
| Printful | `POST /webhook/printful` |
| EasyPost | `POST /webhook/easy-post` |

## The raw-body rule

Signature verification for Stripe and MamoPay runs over the **exact bytes** the provider
sent. If a proxy, body parser, or middleware re-serialises the JSON before it reaches the
handler, verification fails even though the payload is semantically identical.

If you are terminating or forwarding these events in your own infrastructure before they
reach Droplinked, forward the body **unmodified**, and forward the signature header
unmodified with it.

## What a response code means to the sender

| Droplinked returns | What the provider should do |
| - | - |
| `200` | Accepted. Stop retrying. |
| `400` | Rejected permanently — bad signature or unusable payload. Do not retry. |
| `401` | Credential missing or wrong. Fix configuration. |
| `503` | Droplinked could not process it **through no fault of the event**, e.g. a signing secret is briefly unconfigured. **Retry.** Stripe re-delivers for up to three days, which is the intended behaviour here. |

That `400` / `503` split is deliberate: an internal fault must never be reported as a
permanent rejection, because that drops a genuine money event on the floor.

## Replay and duplicate delivery

Providers retry. Assume **at-least-once** delivery and design for duplicates.

Two mechanisms exist on our side, and it is worth knowing which one is load-bearing:

1. **Event-id dedupe** — a per-provider cache keyed on the provider's event id, with a
   24-hour window. It absorbs a provider retry that lands on the same application
   instance. It is in-process: it does **not** span instances and does not survive a
   restart, so do not treat it as a guarantee.
2. **Forward-only order state** — the durable one. Order state machines do not rewind. A
   stale `AUTHORIZED` event arriving after `CAPTURED` is dropped rather than applied, and
   re-applying a terminal transition is a no-op.

The practical consequence for a partner: re-delivering an event is safe, and you do not
need to coordinate with us before replaying a backlog.

## Outbound webhooks

Droplinked can also **dispatch** events to a merchant-registered endpoint, with a
per-subscription signing secret that can be rotated, and a delivery log keyed
`<subscriptionId>:<eventId>` so a redelivery is not double-counted. This surface is not
self-serve today — ask your Droplinked counterpart if you need it.

## Related

* [Order lifecycle](/guides/order-lifecycle.md) — where in the flow the webhook lands
* [Idempotency and retries](/guides/payments/idempotency-and-retries.md)
* [Errors](/api-reference/errors.md)


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.