Webhooks

    Receive subscribe, unsubscribe and bounce events at your own endpoint instead of polling, and build a receiver that survives retries.

    On this page

    A webhook lets IGSendMail tell your application that something happened, instead of your application asking over and over. This page explains the model, what is worth reacting to, and the three requirements a receiving endpoint has to meet before you put it in production.

    Before you start

    You need somewhere to receive the request: a URL on a server you control, reachable from the public internet over HTTPS. A local development machine is not reachable, so you will need a tunnel or a deployed staging environment to test against.

    The model#

    Polling means your code runs on a timer, asks for the current state, and works out what changed since last time. It is wasteful when nothing has happened and slow when something has, because the news waits for your next poll.

    A webhook inverts that. You register a URL. When an event occurs, an HTTP request arrives at your URL describing it. Your code reacts. Nothing runs in between.

    The trade is that you now operate a public endpoint that has to be available, has to answer quickly, and has to be safe against requests that did not come from where you think.

    Configuration is done in the account area, not through the API. Register the URL there, and change or remove it there.

    What to react to#

    Three events cover most integrations.

    A subscribe. Someone joined a list, usually through one of your forms. React by creating or updating the person in your CRM, notifying a sales channel for a high-intent form, or starting your own onboarding process. This is the event that turns a signup form into a lead pipeline without a nightly export.

    An unsubscribe. Someone left. This is the most important one to handle correctly, and the one most often skipped. If your own database still has that person marked as mailable, your next sync re-adds them and you mail someone who asked you to stop. Write the opt-out back to your system of record the moment you hear about it. See Subscribers API for the matching read and write calls.

    A bounce. An address failed. Hard-bounced addresses are suppressed on this side automatically, so the value of the event is in your system: flag the contact record so your support team knows why the customer never got their invoice, and stop your other tools from retrying the same dead address.

    Beyond those, react to what your business actually does something with. An event you receive and ignore is a moving part with no purpose.

    Requirements on the receiving side#

    Respond fast, with a 200

    Answer the request quickly and return HTTP 200. That is the signal that you received it. Anything else, a 500, a timeout, a redirect, is read as a failure, and the delivery will be retried.

    Do not do the work inside the request. If your handler calls your CRM, updates three tables and sends an internal notification before responding, you are one slow third-party API away from timing out on every event. Write the event to a queue or a table, return 200, and process it separately. The handler's only job is to accept the message and record it.

    Make handling idempotent

    Assume every event can arrive more than once. Retries after a timeout are the common cause, and a retry can arrive after your original processing succeeded, because the failure was in your response rather than your work.

    Idempotent means processing the same event twice leaves the same result as processing it once. In practice:

    • Give each incoming event a stable key and record the ones you have handled. If the key is already there, return 200 and stop.
    • Prefer operations that are naturally repeatable. Setting a contact to unsubscribed is safe to repeat. Incrementing a counter or appending a row is not.
    • Do not assume order. Events can arrive out of sequence, so a handler that only makes sense if it runs after another handler will eventually break.

    The failure this prevents is not theoretical. A non-idempotent handler that creates a CRM record on subscribe produces duplicate contacts the first time a delivery is retried, and duplicates are far harder to clean up than to avoid.

    Verify before you trust

    Your endpoint is a public URL. Anyone who finds it can send it anything, and a handler that trusts its input will happily unsubscribe your entire list on request.

    Before acting on a request:

    • Use HTTPS. Plain HTTP exposes the contents in transit and gives you nothing to authenticate against.
    • Make the URL unguessable. Include a long random component in the path or a secret query parameter that you check on arrival. This is a weak control on its own, because the URL travels in logs and headers, but it filters out drive-by traffic.
    • Confirm the claim against the API before doing anything expensive or destructive. The strongest available check is to treat the request as a hint rather than a fact: when an event says an address unsubscribed, read that subscriber back through the API and act on what the API returns. That call is authenticated with your API token, so the answer is trustworthy in a way an inbound request is not.
    • Validate the shape. Reject anything that is not the structure you expect, before it reaches your business logic.

    Never let an inbound request decide something irreversible on its own

    Deleting records, refunding money and disabling accounts should not be triggered directly by a webhook body. Verify against the API first, or queue the action for review. An endpoint that deletes on request is an endpoint that deletes on any request.

    Testing your receiver#

    Register the URL in the account area against a staging list, then cause a real event: subscribe a test address to that list and unsubscribe it again. Watch your logs.

    Check three things. That the request arrived at all, which tells you the URL and firewall are right. That you returned 200 within a sensible time. That running the same event through your handler twice produces one outcome, not two. The third is the one people skip and later regret.

    Keep a log of received events with their timestamps. When somebody asks why a contact's status differs between systems, that log is the only thing that will answer the question.

    If something goes wrong#

    Nothing arrives. Confirm the URL is reachable from outside your network, and that it is registered in the account area against the right list. Then confirm the event you expected actually happened.

    The same event is processed repeatedly. Your endpoint is not returning 200 in time. Look at your response times, then move the work off the request path.

    Duplicate records appear downstream. Your handler is not idempotent. Add the seen-event check before doing anything else.

    Your data drifts out of sync anyway. Webhooks are a live feed, not a guarantee of completeness. If your endpoint was down for an hour, you missed that hour. Keep a periodic reconciliation job that reads the current state through the API and repairs differences.

    Last updated September 10, 2026

    Was this page helpful?