Skip to main content

The short version

Your endpoint has one job: confirm that you received the event. Return a 2xx as soon as you have durably stored the request, then run your business logic separately. Imprint is the sender — we have no visibility into your business rules, so we cannot act on them. We treat every non-2xx response the same way: as “you did not receive this event,” and we retry. Your HTTP status code is a transport-level receipt, not a place to express whether you liked the event.
Returning 422, 404, or 400 does not tell Imprint to stop sending. It only causes the same event to be redelivered until the 10-attempt limit is reached, and then dropped. If a payload is unexpected, still return 2xx, store it, and raise an alert inside your own system.

Why we retry a 4xx at all

This runs against the usual HTTP convention, where a 4xx means “the request was wrong, don’t bother sending it again,” so it is worth stating plainly rather than leaving you to discover it. From the sender’s position, two situations are indistinguishable: an endpoint that has genuinely looked at an event and rejected it, and an endpoint that returned an error by mistake — mid-deploy, a dependency that was briefly down, a validation rule firing on a field it should have ignored. Both answer 422. A retry is cheap and recovers the second case, so Imprint retries and leaves the business decision to you. The cost is that a deliberate rejection also gets retried nine more times, which is why the guidance is to acknowledge first and reject inside your own system instead.

What Imprint sends

Each event is delivered as a single HTTP POST to the URL registered for that event type. The envelope and the data fields of every event type are specified on the Event notifications reference page. This page covers only how the request gets to you. Worth noting is what the request does not carry: no per-delivery sequence number, no retry-attempt counter, and no delivery ID header. Nothing in the request tells you whether you are looking at a first attempt or a ninth — see Idempotency and ordering.

What counts as a successful delivery

A delivery succeeds when your endpoint returns one of these status codes: 200, 201, 202, 203, 204, 205, 206, 207, 208, 226 That is every assigned 2xx code, so in practice: acknowledge with a 2xx. The response body is recorded for support purposes but is never parsed or acted on. Anything else is a failed delivery and is retried:
  • any 3xx, 4xx, or 5xx status — and any non-standard code outside the list above
  • a connection failure, TLS failure, or DNS failure
  • no response at all — the attempt timed out

Retry schedule

Failed deliveries are retried with exponential backoff, up to 10 total attempts (the first delivery plus 9 retries). Backoff doubles after each failure, starting at 5 seconds in production.
These figures are nominal and individual delays can vary. Backoff is measured from the end of the previous attempt, so an endpoint that hangs instead of answering stretches the total window well past what the table shows. Use the table for the shape and the order of magnitude, not as a contract to build timing dependencies on.
In the sandbox environment the schedule is compressed — backoff starts at 1 second and the 10 attempts span roughly 8 to 9 minutes — so you can exercise retry handling without waiting. The backoff is identical for every failure mode. A 422 is retried on exactly the same schedule as a 503.

After the final attempt

Once the 10th attempt fails, Imprint stops. The event is not redelivered later, and there is currently no self-serve way for you to replay it. Imprint Portal shows a graph of webhook delivery, so you can see delivery volume and failures for your own endpoints there. Treat it as a way to confirm or investigate a problem rather than as the thing that tells you a problem started — it will not page you. Your own alerting on your own processing remains the reliable signal. If you know or suspect you dropped events — a bad deploy, an expired certificate, a firewall change — contact your Imprint team so we can arrange a backfill. Include the event type, the approximate time window, and any customer or transaction identifiers you have.

How your endpoint should be built

1

Read the raw body into memory

Buffer the exact bytes you received, with a size limit. Do not persist anything yet, and do not parse and re-serialize — the signature is computed over the bytes as sent, so any reformatting breaks verification.
2

Verify the signature

Verify over those exact bytes, before you store anything and before you trust the payload. Storing first would let anyone who knows your URL fill your database by posting unsigned requests. If verification fails, reject the request — a request that is not from Imprint should not be acknowledged. A genuine Imprint event rejected this way will be retried on the schedule above, so a signature bug surfaces as repeated delivery rather than silent loss. See Verify event signature.
3

Store the verified body

Now persist the raw bytes, along with the X-IMPRINT-HMAC-SIGNATURE header. Keeping the unmodified body is what lets you reprocess later without asking Imprint to resend.
4

Acknowledge

Return 200 or 202 as soon as the event is durably stored, and before any business logic runs. Do not wait on downstream systems, database writes to other services, queue publishes that can block, or any validation of the event’s contents.
5

Process asynchronously

Run your business logic from the stored record — a queue, a job runner, or a worker loop. Retry inside your own system, on your own schedule, with your own alerting. This is the part Imprint’s retries cannot help with, because from our side the delivery already succeeded.
6

Alert on your own failures

If your async processing rejects an event, that is your incident to see, not something to signal by way of an HTTP status. Log it, alert on it, and tell Imprint if the payload itself looks wrong.

Status code reference

The rule is the one already stated: anything that is not a 2xx is retried on the same schedule, then dropped. There is no per-code retry behavior to look up. What this table covers is the handful of codes where that rule has a consequence worth knowing about.

Idempotency and ordering

Delivery is retried, not guaranteed. An event you accept may still be delivered again, and an event can be dropped entirely once the retry budget is exhausted. So plan for duplicates, plan for events arriving out of order, and do not treat “we would have received it” as a safe assumption when reconciling. Duplicates. A retry sends the same body, and since no delivery ID header exists, deduplication has to key off the payload itself. Which fields to use is covered in Handle duplicate events. Two things that section does not tell you, and that follow from how retries work. Duplicates can arrive with no failure on your side at all: if your 2xx is lost in transit after you already committed the event, we retry something you processed perfectly well. And not every event type carries updated_at, so confirm on the reference page that the events you subscribe to actually have one before you rely on it as a tiebreaker. Ordering. Each event is delivered on its own, in parallel with the others. A retrying event does not block newer events, so while attempt 6 of one event is still backing off, later events for the same customer will already have been delivered. Do not infer sequence from arrival order. Drive your state from the identifiers and timestamps in the payload, and make transitions order-independent — ignore an update that would move a record backwards relative to what you have already stored. Signature freshness. The signature and its timestamp are recomputed on every attempt, so a retry carries a current timestamp rather than the timestamp of the first attempt. That is the one retry behavior that changes how you verify a request — Verify event signature covers what it means for freshness windows and why the signature is not a deduplication key.

Checklist

  • Endpoint is HTTPS and accepts POST
  • Signature verified against the raw, unmodified body before any parsing
  • Multiple s= values in the signature header handled (rotation-safe)
  • Raw body persisted before acknowledging
  • 2xx returned before business logic runs, never gated on it
  • Business logic runs asynchronously with its own retries and alerts
  • No business condition can produce a non-2xx response
  • Deduplication keyed on object + the event’s identifier (+ updated_at where present)
  • State transitions are order-independent
  • No redirects on the webhook URL
  • Alerting on your own processing-failure rate, not on Imprint’s retries
  • A contact path to your Imprint team for endpoint changes and backfills