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

# Auto-claim Gifts as Fallback

> Auto-claim a gift on the recipient's behalf when they don't claim it themselves. Covers the fallback flow, webhook cancellation, and 409 handling.

The Auto-claim as Fallback pattern handles the case where a recipient receives a gift but doesn't claim it — perhaps they missed the notification, the email went to spam, or the deadline passed. Instead of letting the gift expire, you claim it on their behalf and fulfill it directly.

## Prerequisites

* API key with `gifts:create` (to create the gift) and `orders:create` (to claim it) scopes
* A plan for what product to order on the recipient's behalf (store this at gift creation time)
* A mechanism to watch for `gift-status-changed` webhooks

## The flow

1. **Create the gift** with `POST /v2/gifts` and store your fallback plan (product/variant + recipient address) in your own database, keyed by `giftId`.
2. **Watch `gift-status-changed` webhooks**. If the recipient claims the gift themselves, cancel your fallback plan — the gift is handled.
3. **Trigger auto-claim** after your deadline (e.g., N days after creation) if the gift is still unclaimed.

## Store the fallback plan at creation time

When you create the gift, immediately record what you'll order if the recipient doesn't claim it:

```javascript JavaScript theme={null}
async function createGiftWithFallback(campaignId, recipient, fallback) {
  const result = await sendGift(campaignId, recipient);
  const giftId = result.results[0].id;

  // Store in your DB: giftId → fallback plan
  await db.giftFallbacks.upsert({
    giftId,
    variantId: fallback.variantId,
    shippingAddress: fallback.shippingAddress,
    scheduledFor: new Date(Date.now() + fallback.daysToWait * 86400000),
    status: "pending",
  });

  return giftId;
}
```

## Cancel the plan if the recipient claims

Subscribe to `gift-status-changed` webhooks. When a gift's status changes to `claimed` (or any terminal state that means the recipient acted), mark your fallback plan as cancelled:

```javascript JavaScript theme={null}
app.post("/webhooks/snappy", async (req, res) => {
  const { webhookData, eventData } = req.body;
  if (webhookData.eventType === "gift-status-changed") {
    const { giftId, status } = eventData;
    if (["claimed", "expired", "canceled"].includes(status)) {
      await db.giftFallbacks.cancel(giftId);
    }
  }
  res.sendStatus(200);
});
```

## Trigger auto-claim

After your deadline, if the fallback plan is still `pending`, claim the gift:

```text theme={null}
POST /v2/gifts/{giftId}/claim
```

```javascript JavaScript theme={null}
async function autoClaim(giftId, { variantId, shippingAddress, recipient }) {
  const res = await fetch(`${SNAPPY}/v2/gifts/${giftId}/claim`, {
    method: "POST",
    headers,
    body: JSON.stringify({
      variantId,
      recipient: {
        firstName: recipient.firstName,
        lastName: recipient.lastName,
        email: recipient.email,
        phone: recipient.phone,       // E.164, required
      },
      shippingAddress: {
        address1: shippingAddress.address1,
        city: shippingAddress.city,
        provinceCode: shippingAddress.provinceCode,
        postalCode: shippingAddress.postalCode,
        countryCode: shippingAddress.countryCode,
      },
    }),
  });

  if (res.status === 409) {
    const error = await res.json();
    return handleClaimConflict(error);
  }

  if (!res.ok) throw new Error(`Claim failed: ${res.status}`);
  return res.json();
}
```

```python Python theme={null}
def auto_claim(gift_id, variant_id, shipping_address, recipient):
    payload = {
        "variantId": variant_id,
        "recipient": {
            "firstName": recipient["first_name"],
            "lastName": recipient["last_name"],
            "email": recipient["email"],
            "phone": recipient["phone"],
        },
        "shippingAddress": {
            "address1": shipping_address["address1"],
            "city": shipping_address["city"],
            "provinceCode": shipping_address["province_code"],
            "postalCode": shipping_address["postal_code"],
            "countryCode": shipping_address["country_code"],
        },
    }
    res = requests.post(f"{SNAPPY}/v2/gifts/{gift_id}/claim", headers=headers, json=payload)

    if res.status_code == 409:
        return handle_claim_conflict(res.json())

    res.raise_for_status()
    return res.json()
```

## 409 handling

The claim endpoint returns `409 Conflict` in two distinct situations:

| `errorCode`    | Meaning                                                 | Action                                                |
| :------------- | :------------------------------------------------------ | :---------------------------------------------------- |
| `409_PBLC_001` | Gift already has an active order — recipient claimed it | Cancel your fallback plan; order is already in flight |
| `409_ORDS_001` | Gift already expired                                    | Log and stop; gift is no longer claimable             |

```javascript JavaScript theme={null}
function handleClaimConflict(error) {
  if (error.errorCode === "409_PBLC_001") {
    console.log("Gift already claimed by recipient");
    return { alreadyClaimed: true };
  }
  if (error.errorCode === "409_ORDS_001") {
    console.log("Gift already expired");
    return { expired: true };
  }
  throw new Error(`Unexpected 409: ${JSON.stringify(error)}`);
}
```

Both 409 cases are terminal — retrying will not succeed. Handle them gracefully without alerting as errors.

## Related

* [Send Triggered Gifts](/patterns/send-triggered-gifts) — creating the gift that this pattern wraps
* [Track Order Fulfillment](/patterns/track-order-fulfillment) — tracking the order after a successful claim
